如何序列化async / await?
让我们假设我有这个简单的片段:
async void button_Click(object sender, RoutedEventArgs e) { await Task.Factory.StartNew(() => { Console.WriteLine("start"); Thread.Sleep(5000); Console.WriteLine("end"); }); }
显然,每次按下该按钮,即使前一个任务仍在运行,也会启动一个新任务。 在所有先前的任务完成之前,我将如何推迟任何新任务?
更多细节:
在上面的示例中,每个新任务都与之前的任务相同。 但是,在原始上下文中,任务序列很重要:参数可能会发生变化(我可以通过使用DateTime.Now.Ticks
来“模拟”它)。 任务应按照“注册”的顺序执行。 具体来说,我的程序将与串行设备通信。 我之前使用BlockingCollection
的后台线程完成了这个。 但是,这次有一个严格的请求/响应协议,如果有可能,我想使用async / await。
可能的方法:
我可以想象创建任务并将它们存储在列表中。 但是,我如何执行与要求相关的任务? 或者我应该回到之前使用的基于线程的解决方案?
您可以异步等待SemaphoreSlim
并在作业完成后释放它。 不要忘记将信号量initialcount配置为1
。
private static SemaphoreSlim semaphore = new SemaphoreSlim(1); private async static void DoSomethingAsync() { await semaphore.WaitAsync(); try { await Task.Factory.StartNew(() => { Console.WriteLine("start"); Thread.Sleep(5000); Console.WriteLine("end"); }); } finally { semaphore.Release(); } } private static void Main(string[] args) { DoSomethingAsync(); DoSomethingAsync(); Console.Read(); }
我建议使用SemaphoreSlim
进行同步。 但是,你想避免Task.Factory.StartNew
(正如我在我的博客上解释的那样),也绝对避免async void
(正如我在MSDN文章中解释的那样)。
private SemaphoreSlim _mutex = new SemaphoreSlim(1); async void button_Click(object sender, RoutedEventArgs e) { await Task.Run(async () => { await _mutex.WaitAsync(); try { Console.WriteLine("start"); Thread.Sleep(5000); Console.WriteLine("end"); } finally { _mutex.Release(); } }); }
如何尝试使用(默认)最大并行度为1的Dataflow.ActionBlock
。这样您就不必担心任何线程安全/锁定问题。
它可能看起来像:
... var _block = new ActionBlock(async b => { Console.WriteLine("start"); await Task.Delay(5000); Console.WriteLine("end"); }); ... async void button_Click(object sender, RoutedEventArgs e) { await _block.SendAsync(true); }
您还可以设置ActionBlock来接收Task
或Func
,然后只需运行/等待此输入。 这将允许多个操作从不同的源排队等待。
我可能会遗漏一些东西,但我不认为OP的情况需要SemaphoreSlim
。 我会按照以下方式做到这一点。 基本上,代码只是在继续之前等待任务的先前待处理实例(为清晰起见,没有exception处理):
// the current pending task (initially a completed stub) Task _pendingTask = Task.FromResult(true); async void button_Click(object sender, RoutedEventArgs e) { var previousTask = _pendingTask; _pendingTask = Task.Run(async () => { await previousTask; Console.WriteLine("start"); Thread.Sleep(5000); Console.WriteLine("end"); }); // the following "await" is optional, // you only need it if you have other things to do // inside "button_Click" when "_pendingTask" is completed await _pendingTask; }
[更新]要解决注释,这里是一个线程安全的版本,当可以同时调用button_Click
时:
Task _pendingTask = Task.FromResult(true); object _pendingTaskLock = new Object(); async void button_Click(object sender, RoutedEventArgs e) { Task thisTask; lock (_pendingTaskLock) { var previousTask = _pendingTask; // note the "Task.Run" lambda doesn't stay in the lock thisTask = Task.Run(async () => { await previousTask; Console.WriteLine("start"); Thread.Sleep(5000); Console.WriteLine("end"); }); _pendingTask = thisTask; } await thisTask; }