试一试。使用单个按钮设置新的Windows窗体应用程序,并为按钮单击事件设置以下代码:
private async void button1_Click(object sender, EventArgs e)
{
using (var file = File.OpenRead(@"C:\Temp\Sample.txt"))
{
byte[] buffer = new byte[4096];
int threadId = Thread.CurrentThread.ManagedThreadId;
int read = await file.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
Debug.Assert(threadId != Thread.CurrentThread.ManagedThreadId);
}
}
然后运行应用程序并快速单击按钮。如果您的体验与我的一样,您会发现有时它会按预期工作,但是,有时Debug.Assert
会失败。
根据我对ConfigureAwait
as explained on Stephen Cleary’s blog的理解,为false
传递continueOnCapturedContext
应指示任务不同步回“主”上下文(此处的UI线程) case)并且该执行应该继续在线程池上。
为什么断言会随机失败?我只能假设ReadAsync
有时无法在后台线程上完成,即它将同步完成。
此行为符合KB 156932 - Asynchronous Disk I/O Appears as Synchronous on Windows:
大多数I / O驱动程序(磁盘,通信和其他)都有特殊情况 代码,如果I / O请求可以完成"立即,"该 操作将完成并具有ReadFile或WriteFile功能 将返回TRUE。在所有方面,这些类型的操作似乎都是 同步。对于磁盘设备,通常可以是I / O请求 完成"立即"当数据缓存在内存中时。
我的假设和测试应用是否正确? ReadAsync
方法有时可以同步完成吗?有没有办法保证执行将始终在后台线程上继续?
这个问题对我的应用程序造成了严重破坏,该应用程序使用COM对象,要求我知道哪个线程一直在执行。
Windows 7 64位,.NET 4.5.1
答案 0 :(得分:3)
ReadAsync方法有时可以同步完成吗?
是。由于OS级别和.NET流类型中的缓冲区,这并不罕见。
有没有办法保证执行将始终在后台线程上继续?
如果您总是希望代码在线程池线程上执行,那么请使用Task.Run
:
private async void button1_Click(object sender, EventArgs e)
{
using (var file = File.OpenRead(@"C:\Temp\Sample.txt"))
{
byte[] buffer = new byte[4096];
int threadId = Thread.CurrentThread.ManagedThreadId;
int read = await file.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
await Task.Run(() =>
{
Debug.Assert(threadId != Thread.CurrentThread.ManagedThreadId);
}).ConfigureAwait(false);
}
}
ConfigureAwait
是一个优化提示,而不是移动到后台线程的命令。如果操作已经完成,则ConfigureAwait
无效。在这种情况下,await
遵循"fast path",这实际上意味着它会继续同步,因此会忽略任何ConfigureAwait
提示。
答案 1 :(得分:2)
如果文件读取在调用await之前同步完成,我相当确定没有异步编译器魔术发生并且执行在调用线程上同步继续。如果我想保证它在线程池线程上启动,我会使用Task.Run(() => file.ReadAsync(buffer, 0, buffer.Length))
将工作排队到线程池。
答案 2 :(得分:0)
在await
上使用Task
时,如果任务已完成,则代码将同步执行。然后在该任务上调用ConfigureAwait
将没有任何区别,因为不会涉及其他线程。
听起来好像数据在内存中(因为你正在重复读取同一个文件)file.ReadAsync
可以同步完成,因此它将在同一个线程上完成。