异步任务和锁

时间:2015-01-02 05:15:07

标签: c# .net multithreading asynchronous async-await

我有一个应该由两个进程更新的元素列表。第一个是UI线程(由用户控制),第二个是从Web服务检索信息的后台进程。

由于第二个进程受I / O限制,因此它似乎适用于异步任务。这引出了几个问题:

  1. 由于异步任务不在不同的线程上运行,所以在更新此列表时我似乎不需要任何锁定,对吗?

  2. 另一方面,我们可以假设异步任务永远不会在不同的线程上运行吗?

  3. 我在谈论Windows窗体应用程序。也许在将来我希望它作为控制台应用程序运行它。 AFAIK,在控制台应用程序中异步任务在不同的线程上运行。如果一个任务在一个单独的线程上运行,那么首选的成语是什么?这样我就可以在必要时建立锁定。

  4. 事实上,我不知道我是否真的需要锁,这让我觉得这是不是最好的设计。即使对于这种IO绑定代码,坚持Task.Run()是否有意义?

3 个答案:

答案 0 :(得分:8)

  

由于异步任务不在不同的线程上运行,因此我似乎并不需要   更新此列表时是否有任何锁定,对吗?

无法保证不会使用Task.Run并标记其方法async。 IO绑定的异步任务很可能在幕后没有使用某种线程,but that isn't always the case.您不应该依赖它来保证代码的正确性。您可以通过使用另一个不使用async的{​​{1}}方法将代码包装在UI线程上来确保代码运行。您始终可以使用框架中提供的并发集合。也许ConfigureAwait(false)ConcurrentBag可以满足您的需求。

  

AFAIK,在控制台应用程序中异步任务在不同的线程上运行。   如果任务在一个单独的地方运行,那么首选的成语是什么?   线程?

这是不正确的。 BlockingCollection他们自己的操作不会在单独的线程上运行,因为他们只是在控制台应用程序中。简单地说,控制台应用程序中的默认async是默认的TaskScheduler,它将在线程池线程上排队任何延续,因为控制台没有这样的实体称为ui线程。通常,it's all about SynchronizationContext

  

事实上,我不知道我是否真的需要锁定让我感到奇怪   这是最好的设计与否。坚持下去是否有意义   Task.Run()甚至对于这种IO绑定代码?

绝对不是。您不知道的事实是您发布此问题的原因,以及我们尝试提供帮助的原因。

不需要使用线程池线程来执行异步IO。 IO中异步的全部意义在于,您可以释放执行IO的调用线程,以便在处理请求时处理更多工作

答案 1 :(得分:5)

  

由于异步任务不会在不同的线程上运行,因此在更新此列表时我似乎不需要任何锁定,对吗?

真的。如果您遵循功能模式(即,每个后台操作将返回其结果,而不是更新共享数据),此方法很有效。所以,这样的事情会很好:

async Task BackgroundWorkAsync() // Called from UI thread
{
  while (moreToProcess)
  {
    var item = await GetItemAsync();
    Items.Add(item);
  }
}

在这种情况下,GetItemAsync的实施方式并不重要。它可以使用Task.RunConfigureAwait(false)所需的全部内容 - BackgroundWorkAsync将始终与UI线程同步,然后再将项目添加到集合中。

  

也许将来我希望它作为控制台应用程序运行它。 AFAIK,在控制台应用程序中异步任务在不同的线程上运行。

"异步任务"完全没有运行。如果这令人困惑,我有async intro可能会有所帮助。

每个异步方法都开始同步执行。当它到达await时,它(默认情况下)捕获当前上下文,然后使用它来继续执行该方法。因此,从UI线程调用它时会发生的事情是async方法在捕获的UI上下文中恢复。控制台应用程序不提供上下文,因此async方法在线程池线程上恢复。

  

如果任务在一个单独的线程上运行,那么首选的成语是什么?这样我就可以在必要时建立锁定。

我建议设计不会询问此类线程问题。首先,您可以使用普通lock - 当没有争用时,他们会非常

async Task BackgroundWorkAsync() // Called from any thread
{
  while (moreToProcess)
  {
    var item = await GetItemAsync();
    lock (_mutex)
        Items.Add(item);
  }
}

或者,您可以记录该组件依赖于提供的一次一个上下文,并使用AsyncContext from my AsyncEx library之类的内容作为控制台应用程序。

答案 2 :(得分:1)

Asyc-awaitawait语句之前捕获同步上下文,然后默认情况下在await语句之后在同一上下文中运行continuation。 UI线程的同步上下文仅与一个线程相关联,因此在这种情况下,您可能最终总是从UI线程更新列表。

但如果有人在等待之后更改代码以调用ConfigureAwait(false),则继续将不会在原始同步上下文上运行,并且您最终可以更新其中一个线程池线程上的列表。

另请注意,您无法在await语句中使用lock,但可以使用SemapahoreSlim代替异步等待。

  1. 恕我直言,最好只使用synchronized collection,而不是依赖于从同一个帖子更新的列表。

  2. 您无法假设,将捕获当前的同步上下文,但可能无法始终在其上运行。

  3. 我会使用同步集合或SempahoreSlim。对于控制台应用程序,使用线程池synchronizationo上下文,并且continuation可能最终在任何线程池线程上运行。

  4. 对IO绑定代码使用async-await是有意义的,因为它不使用线程。

  5. 我坚持使用async-await并更改为使用线程安全集合或使用SemaphoreSlim进行同步