使用ContinueWith或Async-Await时的不同行为
当我在HttpClient调用中使用async-await方法(如下例所示)时,此代码会导致死锁 。 用t.ContinueWith
替换async-await方法,它可以正常工作。 为什么?
public class MyFilter: ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { var user = _authService.GetUserAsync(username).Result; } } public class AuthService: IAuthService { public async Task GetUserAsync (string username) { var jsonUsr = await _httpClientWrp.GetStringAsync(url).ConfigureAwait(false); return await JsonConvert.DeserializeObjectAsync(jsonUsr); } }
这有效:
public class HttpClientWrapper : IHttpClient { public Task GetStringAsync(string url) { return _client.GetStringAsync(url).ContinueWith(t => { _log.InfoFormat("Response: {0}", url); return t.Result; }); }
此代码将死锁:
public class HttpClientWrapper : IHttpClient { public async Task GetStringAsync(string url) { string result = await _client.GetStringAsync(url); _log.InfoFormat("Response: {0}", url); return result; } }
我在我的博客和最近的MSDN文章中描述了这种死锁行为。
因此,当HTTP请求完成时,它会尝试输入SynchronizationContext
以运行InfoFormat
。 但是, SynchronizationContext
已经存在一个线程 – 在Result
阻塞的线程(等待async
方法完成)。
另一方面,默认情况下, ContinueWith
的默认行为将调度其继续当前的TaskScheduler
(在本例中是线程池TaskScheduler
)。
正如其他人所指出的那样,最好使用await
“一直”,即不要阻止async
代码。 不幸的是,在这种情况下,这不是一个选项,因为MVC不支持异步操作filter (作为附注,请在此投票支持此支持 )。
因此,您可以选择使用ConfigureAwait(false)
或仅使用同步方法。 在这种情况下,我建议使用同步方法。 ConfigureAwait(false)
仅在其应用的Task
尚未完成时才有效,所以我建议一旦你使用ConfigureAwait(false)
,你应该在该点之后的方法中为每个await
使用它(在这种情况下,每个调用堆栈中的方法)。 如果出于效率原因正在使用ConfigureAwait(false)
,那么这很好(因为它在技术上是可选的)。 在这种情况下,出于正确原因,必须使用ConfigureAwait(false)
,因此IMO会产生维护负担。 同步方法会更清晰。
解释你为何等待死锁的原因
你的第一行:
var user = _authService.GetUserAsync(username).Result;
在等待GetUserAsync
的结果时阻塞该线程和当前上下文。
当使用await
时,它会在完成任务等待后尝试在原始上下文中运行任何剩余的语句,如果原始上下文被阻塞(这是因为.Result
),则会导致死锁。 看起来您试图通过在GetUserAsync
使用.ConfigureAwait(false)
来抢占此问题,但是当await
生效时,为时已晚,因为首先遇到另一个await
。 实际执行路径如下所示:
_authService.GetUserAsync(username) _httpClientWrp.GetStringAsync(url) // not actually awaiting yet (it needs a result before it can be awaited) await _client.GetStringAsync(url) // here's the first await that takes effect
当_client.GetStringAsync
完成时,其余代码无法在原始上下文中继续,因为该上下文被阻止。
为什么ContinueWith表现不同
ContinueWith
不会尝试在原始上下文中运行其他块(除非您使用其他参数告诉它),因此不会遇到此问题。
这是您注意到的行为差异。
一个异步的解决方案
如果您仍想使用async
而不是ContinueWith
,则可以将.ConfigureAwait(false)
添加到第一个遇到的async
:
string result = await _client.GetStringAsync(url).ConfigureAwait(false);
您可能已经知道的,告诉await
不要尝试在原始上下文中运行剩余的代码。
注意将来
尽可能尝试在使用async / await时不使用阻塞方法。 请参阅调用异步方法时防止死锁,而不使用await以避免将来出现此问题。
当然,我的回答只是部分,但无论如何我都会继续。
您的Task.ContinueWith(...)
调用未指定调度程序,因此将使用TaskScheduler.Current
– 无论当时是什么。 但是, await
片段将在等待任务完成时在捕获的上下文上运行,因此两位代码可能会也可能不会产生类似的行为 – 具体取决于TaskScheduler.Current
的值。
例如,如果直接从UI代码调用您的第一个代码段(在这种情况下, TaskScheduler.Current == TaskScheduler.Default
,则继续(日志记录代码)将在默认的TaskScheduler
上执行 – 即在线程池上执行。
但是,在第二个片段中,无论是否对GetStringAsync返回的任务使用ConfigureAwait(false)
,继续(日志记录)实际上都将在UI线程上运行。 在等待对GetStringAsync
的调用之后 , ConfigureAwait(false)
将仅影响代码的执行。
这是另一个说明这一点:
private async void Form1_Load(object sender, EventArgs e) { await this.Blah().ConfigureAwait(false); // InvalidOperationException here. this.Text = "Oh noes, I'm no longer on the UI thread."; } private async Task Blah() { await Task.Delay(1000); this.Text = "Hi, I'm on the UI thread."; }
给定的代码将Blah()中的Text设置得很好,但它会在Load处理程序的continuation中抛出一个跨线程exception。
我发现这里发布的其他解决方案在ASP .NET MVC 5上没有用,它仍然使用同步动作filter。 发布的解决方案不保证将使用新线程,它们只是指定不必使用相同的线程。
我的解决方案是使用Task.Factory.StartNew()并在方法调用中指定TaskCreationOptions.LongRunning。 这可确保始终使用新的/不同的线程,因此您可以放心,您永远不会陷入僵局。
因此,使用OP示例,以下是适合我的解决方案:
public class MyFilter: ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { // var user = _authService.GetUserAsync(username).Result; // Guarantees a new/different thread will be used to make the enclosed action // avoiding deadlocking the calling thread var user = Task.Factory.StartNew( () => _authService.GetUserAsync(username).Result, TaskCreationOptions.LongRunning).Result; }
}
上述就是C#学习教程:使用ContinueWith或Async-Await时的不同行为分享的全部内容,如果对大家有所用处且需要了解更多关于C#学习教程,希望大家多多关注—计算机技术网(www.ctvol.com)!
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/cdevelopment/1303146.html