我有一个应该由两个进程更新的元素列表。第一个是UI线程(由用户控制),第二个是从Web服务检索信息的后台进程。
由于第二个进程受I / O限制,因此它似乎适用于异步任务。这引出了几个问题:
由于异步任务不在不同的线程上运行,所以在更新此列表时我似乎不需要任何锁定,对吗?
另一方面,我们可以假设异步任务永远不会在不同的线程上运行吗?
我在谈论Windows窗体应用程序。也许在将来我希望它作为控制台应用程序运行它。 AFAIK,在控制台应用程序中异步任务在不同的线程上运行。如果一个任务在一个单独的线程上运行,那么首选的成语是什么?这样我就可以在必要时建立锁定。
事实上,我不知道我是否真的需要锁,这让我觉得这是不是最好的设计。即使对于这种IO绑定代码,坚持Task.Run()
是否有意义?
答案 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.Run
或ConfigureAwait(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-await
在await
语句之前捕获同步上下文,然后默认情况下在await
语句之后在同一上下文中运行continuation。 UI线程的同步上下文仅与一个线程相关联,因此在这种情况下,您可能最终总是从UI线程更新列表。
但如果有人在等待之后更改代码以调用ConfigureAwait(false)
,则继续将不会在原始同步上下文上运行,并且您最终可以更新其中一个线程池线程上的列表。
另请注意,您无法在await
语句中使用lock
,但可以使用SemapahoreSlim
代替异步等待。
恕我直言,最好只使用synchronized collection,而不是依赖于从同一个帖子更新的列表。
您无法假设,将捕获当前的同步上下文,但可能无法始终在其上运行。
我会使用同步集合或SempahoreSlim
。对于控制台应用程序,使用线程池synchronizationo上下文,并且continuation可能最终在任何线程池线程上运行。
对IO绑定代码使用async-await是有意义的,因为它不使用线程。
我坚持使用async-await
并更改为使用线程安全集合或使用SemaphoreSlim
进行同步