我试图在一个非常大的已经存在的同步代码库中使用async / await。在这个代码库中有一些全局状态,如果在同步上下文中运行正常,但在异步/等待的异步上下文中不起作用。
所以,我的两个选择似乎是要么将全局背景分解出来,这可能是一项非常庞大且非常耗时的任务,或者在继续运行时做一些聪明的事情。
为了更好地理解async / await和continuations,我做了一个测试程序,如图所示。如图所示。
// A method to simulate an Async read of the database.
private static Task ReadAsync()
{
return Task.Factory.StartNew(() =>
{
int max = int.MaxValue / 2;
for (int i = 0; i < max; ++i)
{
}
});
}
// An async method that creates several continuations.
private static async Task ProcessMany(int i)
{
Console.WriteLine(string.Format("{0} {1}", i.ToString(), 0));
await ReadAsync();
Console.WriteLine(string.Format("{0} {1}", i.ToString(), 1));
await ReadAsync();
Console.WriteLine(string.Format("{0} {1}", i.ToString(), 2));
await ReadAsync();
Console.WriteLine(string.Format("{0} {1}", i.ToString(), 3));
}
public static void Main(string[] args)
{
Queue<Task> queue = new Queue<Task>();
for (int i = 0; i < 10; ++i)
{
queue.Enqueue(ProcessMany(i));
}
// Do some synchonous processing...
Console.WriteLine("Processing... ");
for (int i = 0; i < int.MaxValue; ++i)
{
}
Console.WriteLine("Done processing... ");
queue.Dequeue().Wait();
}
在阅读了所有关于async / await的内容之后,我的理解是在“Processing ..”和“Done processing ...”WriteLines之间不会发生任何延续。
以下是一些示例输出。
0 0
1 0
2 0
3 0
4 0
5 0
6 0
7 0
8 0
9 0
Processing...
3 1
2 1
7 1
6 1
0 1
4 1
5 1
1 1
6 2
3 2
Done processing...
7 2
2 2
0 2
4 2
5 2
1 2
6 3
3 3
7 3
2 3
0 3
我希望程序结束时的单个Wait()在第一个完成时可能会产生多个延续,但我不明白在“Processing ...”和“完成处理......”。我认为在Console.WriteLine方法中可能有一个产量或者某些东西,所以我完全取代了它,但这并没有改变输出。
我对async / await的理解显然存在差距。当我们简单地增加一个变量时,怎么会继续发生?编译器或CLR在这里注入某种魔力吗?
提前感谢您提供有关更好地理解async / await和continuations的任何帮助。
编辑:
如果您根据Stephen的评论以这种方式编辑示例代码,那么更明显的是什么。
// An async method that creates several continuations.
private static async Task ProcessMany(int i)
{
Console.WriteLine(string.Format("{0} {1} {2}", i.ToString(), 0, Thread.CurrentThread.ManagedThreadId));
await ReadAsync();
Console.WriteLine(string.Format("{0} {1} {2}", i.ToString(), 1, Thread.CurrentThread.ManagedThreadId));
await ReadAsync();
Console.WriteLine(string.Format("{0} {1} {2}", i.ToString(), 2, Thread.CurrentThread.ManagedThreadId));
await ReadAsync();
Console.WriteLine(string.Format("{0} {1} {2}", i.ToString(), 3, Thread.CurrentThread.ManagedThreadId));
}
public static void Main(string[] args)
{
Queue<Task> queue = new Queue<Task>();
for (int i = 0; i < 10; ++i)
{
queue.Enqueue(ProcessMany(i));
}
// Do some synchonous processing...
Console.WriteLine("Processing... {0}", Thread.CurrentThread.ManagedThreadId);
for (int i = 0; i < int.MaxValue; ++i)
{
}
Console.WriteLine("Done processing... {0}", Thread.CurrentThread.ManagedThreadId);
queue.Dequeue().Wait();
}
输出:
0 0 9
1 0 9
2 0 9
3 0 9
4 0 9
5 0 9
6 0 9
7 0 9
8 0 9
9 0 9
Processing... 9
4 1 14
3 1 13
2 1 12
5 1 15
0 1 10
6 1 16
1 1 11
7 1 17
4 2 14
3 2 13
0 2 10
6 2 16
2 2 12
5 2 15
Done processing... 9
1 2 11
7 2 17
0 3 10
4 3 14
答案 0 :(得分:5)
如果您没有当前SynchronizationContext
或TaskScheduler
,则延续将在线程池线程(与主线程分开)上执行。在控制台应用程序中就是这种情况,但您会在WinForms / WPF / ASP.NET中看到非常不同的行为。
虽然可以通过使用自定义TaskScheduler
来控制延续计划,但这可能会带来很多好处。我不清楚你的全球状态存在什么问题,但考虑SemaphoreSlim
等替代方案。
答案 1 :(得分:0)
只要在ProcessMany中调用以下行,每次对ProcessMany的调用都会立即开始在单独的线程池中的单独线程中执行。
await ...;
这就是为什么你在“Processing”打印输出之前看到一堆调用的原因。因此,当您执行所有10个ProcessMany调用时,您就开始运行大循环。当主线程上运行大循环时,10个ProcessMany调用继续在其线程中执行,从而产生额外的打印输出。看起来您的ProcessMany调用在主线程循环之前没有完成执行,因此在“完成处理”打印输出后它们会继续吐出更多结果。
我希望能为你澄清事情的顺序。