Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/23.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 使用ContinueWith或Async Wait时的不同行为_C#_.net_Asp.net Mvc_Multithreading_Async Await - Fatal编程技术网

C# 使用ContinueWith或Async Wait时的不同行为

C# 使用ContinueWith或Async Wait时的不同行为,c#,.net,asp.net-mvc,multithreading,async-await,C#,.net,Asp.net Mvc,Multithreading,Async Await,当我在HttpClient调用中使用async await方法(如下例)时,这段代码会导致死锁。将异步等待方法替换为t.ContinueWith,它可以正常工作。为什么? public class MyFilter: ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { var user = _authService.G

当我在HttpClient调用中使用async await方法(如下例)时,这段代码会导致死锁。将异步等待方法替换为
t.ContinueWith
,它可以正常工作。为什么?

public class MyFilter: ActionFilterAttribute {
    public override void OnActionExecuting(ActionExecutingContext filterContext) {
         var user = _authService.GetUserAsync(username).Result;
    }
}

public class AuthService: IAuthService {
    public async Task<User> GetUserAsync (string username) {           
        var jsonUsr = await _httpClientWrp.GetStringAsync(url).ConfigureAwait(false);
        return await JsonConvert.DeserializeObjectAsync<User>(jsonUsr);
    }
}
公共类MyFilter:ActionFilterAttribute{
公共覆盖无效OnActionExecuting(ActionExecutingContext filterContext){
var user=\u authService.GetUserAsync(username).Result;
}
}
公共类AuthService:IAuthService{
公共异步任务GetUserAsync(字符串用户名){
var jsonUsr=await\u httpClientWrp.GetStringAsync(url).ConfigureAwait(false);
return wait JsonConvert.DeserializeObjectAsync(jsonUsr);
}
}
这是有效的:

public class HttpClientWrapper : IHttpClient {
    public Task<string> GetStringAsync(string url) {           
        return _client.GetStringAsync(url).ContinueWith(t => {
                _log.InfoFormat("Response: {0}", url);                    
                return t.Result;
        });
}
public class HttpClientWrapper : IHttpClient {
    public async Task<string> GetStringAsync(string url) {       
        string result = await _client.GetStringAsync(url);
        _log.InfoFormat("Response: {0}", url);
        return result;
    }
}
公共类HttpClientWrapper:IHttpClient{
公共任务GetStringAsync(字符串url){
return\u client.GetStringAsync(url).ContinueWith(t=>{
_InfoFormat(“响应:{0}”,url);
返回t.Result;
});
}
此代码将死锁:

public class HttpClientWrapper : IHttpClient {
    public Task<string> GetStringAsync(string url) {           
        return _client.GetStringAsync(url).ContinueWith(t => {
                _log.InfoFormat("Response: {0}", url);                    
                return t.Result;
        });
}
public class HttpClientWrapper : IHttpClient {
    public async Task<string> GetStringAsync(string url) {       
        string result = await _client.GetStringAsync(url);
        _log.InfoFormat("Response: {0}", url);
        return result;
    }
}
公共类HttpClientWrapper:IHttpClient{
公共异步任务GetStringAsync(字符串url){
string result=await\u client.GetStringAsync(url);
_InfoFormat(“响应:{0}”,url);
返回结果;
}
}

当然,我的答案只是部分,但我还是会继续回答

您的
任务。继续(…)
调用没有指定调度程序,因此
TaskScheduler。将使用Current
,无论当时是什么。但是,当等待的任务完成时,您的
wait
代码片段将在捕获的上下文上运行,因此这两位代码可能会或可能不会产生类似的行为,具体取决于
TaskSch的值eduler.Current

例如,如果直接从UI代码调用第一个代码段(在这种情况下,
TaskScheduler.Current==TaskScheduler.Default
),则将在默认的
TaskScheduler
上执行延续(日志代码)-即在线程池上执行

但是,在第二个代码段中,不管您是否对GetStringAsync返回的任务使用
ConfigureAwait(false)
,延续(日志记录)实际上都将在UI线程上运行。
ConfigureAwait(false)
只会在调用
GetStringAsync
后影响代码的执行

这里还有一些东西可以说明这一点:

    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()中的文本设置得很好,但它在加载处理程序的continuation中引发了一个跨线程异常。

关于等待死锁原因的解释 您的第一行:

 var user = _authService.GetUserAsync(username).Result;
在线程等待
GetUserAsync
的结果时阻止该线程和当前上下文

当使用
wait
时,它会在等待的任务完成后尝试在原始上下文上运行任何剩余语句,如果原始上下文被阻止(这是因为
.Result
),则会导致死锁。看起来您试图通过使用
.configurewait(false)来抢占此问题
GetUserAsync
中,但是当
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
\u client.GetStringAsync
完成时,由于该上下文被阻止,其余代码无法在原始上下文上继续

为什么ContinueWith表现不同
ContinueWith
不会尝试在原始上下文上运行另一个块(除非您使用附加参数告诉它),因此不会出现此问题

这就是你注意到的行为差异

一个异步的解决方案 如果仍要使用
async
而不是
ContinueWith
,则可以将
.configurewait(false)
添加到遇到的第一个
async

string result = await _client.GetStringAsync(url).ConfigureAwait(false);
您很可能已经知道,它告诉
wait
不要尝试在原始上下文中运行剩余的代码

未来的注意事项
如果可能,在使用async/await时尽量不要使用阻塞方法。请参阅以了解将来如何避免这种情况。

我将介绍这种死锁行为和方法

  • 默认情况下,
    await
    将安排其继续在当前的
    SynchronizationContext
    内运行,或者(如果没有
    SynchronizationContext
    )当前的
    TaskScheduler
    (在这种情况下是ASP.NET请求
    SynchronizationContext
    )内运行
  • ASP.NET
    SynchronizationContext
    表示请求上下文,ASP.NET一次只允许该上下文中的一个线程
因此,当HTTP请求完成时,它会尝试进入
SynchronizationContext
以运行
InfoFormat
。但是,
SynchronizationContext
中已经有一个线程-在
Result
上被阻止的线程(等待
async
方法完成)

另一方面,默认情况下,
ContinueWith
的默认行为将其继续调度到当前的
TaskScheduler
(在本例中是线程池
TaskScheduler

正如其他人所指出的,最好使用
wait
“一路”,即不要阻塞
async
代码。不幸的是,在这种情况下,这不是一个选项,因为(请附带说明)

因此,您可以选择使用
ConfigureAwait(false)
或仅使用同步方法。在这种情况下,我建议使用同步方法。
ConfigureAwait(false)
仅在应用它的
任务
尚未完成时有效,因此我建议使用
ConfigureAwait(false)后
,您应该永远使用它