之前,我已经写了一个类似问题的答案:How to ensure code execution in 1 thread after await c#.net
但是需要在应用程序中重用单线程同步的魅力,而无需使用异步代码进行任何阻塞。众所周知,lock其他同步原语不能在异步代码中使用。好吧,除了信号量,但信号量并不是很原始。
要重现问题,您甚至不需要集合,您可以运行以下代码:
static async Task Main(string[] args)
{
await AddAsync(1000);
Console.WriteLine(_counter);
}
static int _counter;
static async Task AddAsync(int count)
{
Task[] tasks = new Task[count];
for (int i = 0; i < count; i++)
tasks[i] = IncrementAsync();
await Task.WhenAll(tasks);
}
static async Task IncrementAsync()
{
await Task.Delay(1);
_counter++;
}
并在控制台中获取例如此输出
969
它应该是1000。这意味着代码不是线程安全的。如果您从 WPF 或 Winforms 中的 UI 线程运行此代码,它将 100% 正常工作,并且始终返回1000. 在通过引用提出的解决方案中,上下文本身的启动看起来非常麻烦,并且对于每次启动,您都需要创建一个新线程或将当前线程提供给上下文。
我想找到一个更简单的解决方案,以便您可以将多线程代码的不同部分中的调用重定向到您自己在特定线程中运行的某种上下文。
解决方案与过去非常相似,略有不同。但它比上一个更容易使用。您可以拥有一个上下文并从应用程序的不同角落重用它。
该解决方案的本质是创建一个上下文,该上下文从池中获取 1 个线程并在其中工作,直到它被处理掉。同时,参考上下文实例,您可以将调用从代码中的任何位置重定向到同一个线程。
你可以将任何代码扔到上下文中,包括同步的,但是这个类的目的是让运行异步方法成为可能,这样方法
await内部的线程在异步调用完成后不会改变线程,就像它一样发生在桌面应用程序的 UI 线程中。对此负责的方法有 2 个重载SendAsync。也就是说,可以使用单个线程的普通集合和其他乐趣而不会在代码中阻塞。这个解决方案,即使对于 UI 应用程序,当您想要像在 UI 线程中一样工作
async/await但同时不加载 UI 本身时也会很有用。我正在查。
控制台输出
请注意,此上下文不处理异常。一般来说,同步上下文本身并不打算用于异常处理,它实际上是在处理它们
await。因此,我没有加载带有处理的代码。但是你应该小心。如果Post您手动发送会引发异常的委托,则上下文将下降并且将不再能够处理调用。Send如果你不手动Post调用它,那么你不需要修改任何东西。我展示了如何完全保护上下文不引发异常。为此,修改几个方法非常简单。
我再说一遍,只有当你从上下文之外抛出异常时,
Send才需要这样的细化。Post如果您只使用SendAsync,则不需要此细化。