Kei*_*ith 96 c# asynchronous task-parallel-library async-await c#-5.0
我有一个多层.Net 4.5应用程序调用一个方法使用C#的新async和await关键字挂起,我不明白为什么.
在底部我有一个异步方法来扩展我们的数据库实用程序OurDBConn(基本上是底层DBConnection和DBCommand对象的包装器):
public static async Task<T> ExecuteAsync<T>(this OurDBConn dataSource, Func<OurDBConn, T> function)
{
string connectionString = dataSource.ConnectionString;
// Start the SQL and pass back to the caller until finished
T result = await Task.Run(
() =>
{
// Copy the SQL connection so that we don't get two commands running at the same time on the same open connection
using (var ds = new OurDBConn(connectionString))
{
return function(ds);
}
});
return result;
}
Run Code Online (Sandbox Code Playgroud)
然后我有一个中级异步方法,调用它来获得一些缓慢运行的总计:
public static async Task<ResultClass> GetTotalAsync( ... )
{
var result = await this.DBConnection.ExecuteAsync<ResultClass>(
ds => ds.Execute("select slow running data into result"));
return result;
}
Run Code Online (Sandbox Code Playgroud)
最后,我有一个同步运行的UI方法(一个MVC动作):
Task<ResultClass> asyncTask = midLevelClass.GetTotalAsync(...);
// do other stuff that takes a few seconds
ResultClass slowTotal = asyncTask.Result;
Run Code Online (Sandbox Code Playgroud)
问题是它永远挂在最后一行.如果我打电话,它也会做同样的事情asyncTask.Wait().如果我直接运行慢速SQL方法大约需要4秒钟.
我期待的行为是,当它到达时asyncTask.Result,如果它没有完成它应该等到它,并且一旦它它应该返回结果.
如果我有一个调试器步骤通过SQL语句完成和lambda函数完成,但return result;行GetTotalAsync则永远无法实现.
知道我做错了什么吗?
有什么建议我需要调查以解决这个问题吗?
这可能是某个地方的僵局,如果是这样,有没有直接找到它的方法?
Jas*_*ski 141
是的,这可能是一个僵局.和TPL一样的常见错误,所以不要感觉不好.
在编写await foo时,默认情况下,运行时会在方法启动的同一SynchronizationContext上调度函数的延续.在英语中,假设您ExecuteAsync从UI线程调用了您的.您的查询在线程池线程上运行(因为您已调用Task.Run),但随后等待结果.这意味着运行时将调度您的" return result;"行以在UI线程上运行,而不是将其调度回线程池.
那么这个僵局怎么样?想象一下,你只需要这段代码:
var task = dataSource.ExecuteAsync(_ => 42);
var result = task.Result;
Run Code Online (Sandbox Code Playgroud)
所以第一行开始了异步工作.第二行然后阻止UI线程.因此,当运行时想要在UI线程上运行"返回结果"行时,在Result完成之前它不能这样做.但是,当然,在返回发生之前不能给出结果.僵局.
这说明了使用TPL的关键规则:当您.Result在UI线程(或其他一些花哨的同步上下文)上使用时,您必须小心确保任务所依赖的任何内容都没有安排到UI线程.否则就会发生恶事.
所以你会怎么做?选项#1用于等待所有地方,但正如你所说,这已经不是一个选择.可用的第二个选项是简单地停止使用await.您可以将两个函数重写为:
public static Task<T> ExecuteAsync<T>(this OurDBConn dataSource, Func<OurDBConn, T> function)
{
string connectionString = dataSource.ConnectionString;
// Start the SQL and pass back to the caller until finished
return Task.Run(
() =>
{
// Copy the SQL connection so that we don't get two commands running at the same time on the same open connection
using (var ds = new OurDBConn(connectionString))
{
return function(ds);
}
});
}
public static Task<ResultClass> GetTotalAsync( ... )
{
return this.DBConnection.ExecuteAsync<ResultClass>(
ds => ds.Execute("select slow running data into result"));
}
Run Code Online (Sandbox Code Playgroud)
有什么不同?现在没有任何等待,所以没有任何隐式安排到UI线程.对于像这样只有单一回报的简单方法,做" var result = await...; return result"模式没有意义; 只需删除异步修改器并直接传递任务对象.如果没有别的话,它的开销就会减少.
选项#3是指定您不希望等待安排回UI线程,而只是安排到UI线程.您可以使用此ConfigureAwait方法执行此操作,如下所示:
public static async Task<ResultClass> GetTotalAsync( ... )
{
var resultTask = this.DBConnection.ExecuteAsync<ResultClass>(
ds => return ds.Execute("select slow running data into result");
return await resultTask.ConfigureAwait(false);
}
Run Code Online (Sandbox Code Playgroud)
等待任务通常会安排到UI线程,如果你在它上面; 等待结果ContinueAwait将忽略您所处的任何上下文,并始终安排到线程池.这样做的缺点是你必须在你的.Result所依赖的所有函数中随处可见,因为任何错过的.ConfigureAwait可能是另一个死锁的原因.
Ste*_*ary 34
这是我在博客上描述的经典混合async死锁场景.杰森很好地描述了它:默认情况下,每次都保存一个"上下文" 并用于继续该方法.这个"上下文"是除非它的当前情况,在这种情况下它是当前的.当该方法尝试继续时,它首先重新进入捕获的"上下文"(在本例中为ASP.NET ).ASP.NET一次只允许上下文中的一个线程,并且上下文中已经存在一个线程 - 被阻塞的线程.awaitasyncSynchronizationContextnullTaskSchedulerasyncSynchronizationContextSynchronizationContextTask.Result
有两条准则可以避免这种僵局:
async.你提到你"不能"这样做,但我不确定为什么不这样做..NET 4.5上的ASP.NET MVC当然可以支持async操作,并且它不是一个很难做出的改变.ConfigureAwait(continueOnCapturedContext: false)尽可能多地.这将覆盖在捕获的上下文中恢复的默认行为.Dan*_*low 11
我处于相同的死锁情况,但在我的情况下从同步方法调用异步方法,对我有用的是:
private static SiteMetadataCacheItem GetCachedItem()
{
TenantService TS = new TenantService(); // my service datacontext
var CachedItem = Task.Run(async ()=>
await TS.GetTenantDataAsync(TenantIdValue)
).Result; // dont deadlock anymore
}
Run Code Online (Sandbox Code Playgroud)
这是一个好方法,任何想法?
只是为了添加到已接受的答案(没有足够的代表来评论),我在阻止使用task.Result, 事件时出现了这个问题,尽管await下面的每个事件都有ConfigureAwait(false),如本例所示:
public Foo GetFooSynchronous()
{
var foo = new Foo();
foo.Info = GetInfoAsync.Result; // often deadlocks in ASP.NET
return foo;
}
private async Task<string> GetInfoAsync()
{
return await ExternalLibraryStringAsync().ConfigureAwait(false);
}
Run Code Online (Sandbox Code Playgroud)
问题实际上在于外部库代码。无论我如何配置等待,异步库方法都会尝试在调用同步上下文中继续,从而导致死锁。
因此,答案是滚动我自己的外部库代码版本ExternalLibraryStringAsync,以便它具有所需的延续属性。
经过一番痛苦和痛苦之后,我找到了隐藏在这篇博文中的解决方案(Ctrl-f 表示“死锁”)。它围绕使用task.ContinueWith,而不是裸露task.Result。
之前的死锁示例:
public Foo GetFooSynchronous()
{
var foo = new Foo();
foo.Info = GetInfoAsync.Result; // often deadlocks in ASP.NET
return foo;
}
private async Task<string> GetInfoAsync()
{
return await ExternalLibraryStringAsync().ConfigureAwait(false);
}
Run Code Online (Sandbox Code Playgroud)
避免这样的僵局:
public Foo GetFooSynchronous
{
var foo = new Foo();
GetInfoAsync() // ContinueWith doesn't run until the task is complete
.ContinueWith(task => foo.Info = task.Result);
return foo;
}
private async Task<string> GetInfoAsync
{
return await ExternalLibraryStringAsync().ConfigureAwait(false);
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
49313 次 |
| 最近记录: |