相关疑难解决方法(0)

异步库最佳实践:ConfigureAwait(false)与设置同步上下文

众所周知,在通用库中,ConfigureAwait(false)应该在每次await调用时使用,以避免继续当前的SynchronizationContext.

作为使用整个代码库的替代方法ConfigureAwait(false),可以在公共表面方法中将SynchronizationContext设置为null一次,并在返回给用户之前将其恢复.换一种说法:

public async Task SomeSurfaceMethod()
{
    var callerSyncCtx = SynchronizationContext.Current;
    SynchronizationContext.SetSynchronizationContext(null);
    try
    {
        // Do work
    }
    finally
    {
        SynchronizationContext.SetSynchronizationContext(callerSyncCtx);
    }
}
Run Code Online (Sandbox Code Playgroud)

这也可以包含在内以using获得更好的可读性.

这种方法是否有缺点,它最终不会产生相同的效果吗?

主要优点显然是可读性 - 删除所有ConfigureAwait(false)呼叫.它还可以降低忘记ConfigureAwait(false)某个地方的可能性(尽管分析人员可以减轻这种情况,并且可以说开发人员也可以忘记更改SynchronizationContext).

一个有点奇特的优点是没有嵌入在所有方法中捕获SynchronizationContext的选择.换句话说,在一种情况下,我可能想要使用SynchronizationContext运行方法X,而在另一种情况下,我可能想要运行相同的方法而没有一个.何时ConfigureAwait(false)嵌入到不可能的地方.当然,这是一个非常罕见的要求,但我在处理Npgsql时遇到了这个问题(触发了这个问题).

c# asynchronous async-await

14
推荐指数
1
解决办法
1118
查看次数

使用ConfigureAwait进行C#async/await链接(false)

基于众多书籍和博客,包括这里的优秀书籍,很明显当一个人写一个暴露助手异步方法的DLL库,即包装器方法时,通常认为内部完成实际异步方法的I/O任务的最佳实践在这样的线程池线程上(为了简洁起见,下面显示了伪代码,我将其HttpClient用作示例)

public Async Task<HttpResponseMessage> MyMethodAsync(..)
{
    ...
    var httpClient = new HttpClient(..);
    var response = await httpClient.PostAsJsonAsync(..).ConfigureAwait(false);
    ...
    return response;
}
Run Code Online (Sandbox Code Playgroud)

这里的关键是使用ConfigureAwait(false)IO任务完成发生在线程池线程而不是原始线程上下文,从而可能防止死锁.

我的问题是从来电者的角度来看.我对调用者和上面的方法调用之间存在多层方法调用的情况特别感兴趣,如下例所示.

CallerA -> Method1Async -> Method2Async -> finally the above MyMethodAsync
Run Code Online (Sandbox Code Playgroud)

是仅仅ConfigureAwait(false)使用最终方法还是应该确保Method1Async并在Method2Async内部调用其异步方法ConfigureAwait(false)?将它包含在所有这些中间方法中似乎很愚蠢,特别是如果Method1Async并且Method2Async只是最终调用的重载MyMethodAsync.有任何想法,请指教!

更新了示例 所以如果我有一个带有以下私有异步方法的库,

private async Task<string> MyPrivateMethodAsync(MyClass myClass)
{
    ...
    return await SomeObject.ReadAsStringAsync().ConfigureAwait(false);
}
Run Code Online (Sandbox Code Playgroud)

我应该确保以下公共重载方法还包括ConfigureAwait(false),如下所示?

public async Task<string> MyMethodAsync(string from)
{
        return await MyPrivateMethodAsync(new (MyClass() { From …
Run Code Online (Sandbox Code Playgroud)

.net c# task-parallel-library async-await io-completion-ports

9
推荐指数
2
解决办法
6370
查看次数

Task.Run 继续在同一线程上导致死锁

考虑以下我将同步等待的异步方法。等一下,我知道。我知道这被认为是不好的做法并导致死锁,但我完全意识到这一点,并采取措施通过用Task.Run包装代码来防止死锁。

    private async Task<string> BadAssAsync()
    {
        HttpClient client = new HttpClient();

        WriteInfo("BEFORE AWAIT");

        var response = await client.GetAsync("http://google.com");

        WriteInfo("AFTER AWAIT");

        string content = await response.Content.ReadAsStringAsync();

        WriteInfo("AFTER SECOND AWAIT");

        return content;
    }
Run Code Online (Sandbox Code Playgroud)

这段代码肯定会死锁(与SyncronizationContext环境-它就像ASP.NET单个线程调度任务),如果调用这样的:BadAssAsync().Result

我面临的问题是,即使使用这个“安全”包装器,它仍然偶尔会出现死锁。

    private T Wait1<T>(Func<Task<T>> taskGen)
    {
        return Task.Run(() =>
        {
            WriteInfo("RUN");

            var task = taskGen();

            return task.Result;
        }).Result;
    }
Run Code Online (Sandbox Code Playgroud)

这些“WriteInfo”行是故意的。这些调试行让我看到它偶尔发生的原因是 中的代码Task.Run,有些神秘,是由开始服务请求的同一个线程执行的。这意味着它具有 AspNetSynchronizationContext 作为SyncronizationContext并且肯定会死锁。

这是调试输出:

***(工作正常)
开始:TID:17;SCTX:System.Web.AspNetSynchronizationContext;调度程序:System.Threading.Tasks.ThreadPoolTask​​Scheduler
运行:TID:45;SCTX:<null> 调度程序:System.Threading.Tasks.ThreadPoolTask​​Scheduler
等待之前:TID:45;SCTX:<null> 调度程序:System.Threading.Tasks.ThreadPoolTask​​Scheduler
等待后:TID:37;SCTX:<null> 调度程序:System.Threading.Tasks.ThreadPoolTask​​Scheduler
第二次等待后:TID:37;SCTX:<null> 调度程序:System.Threading.Tasks.ThreadPoolTask​​Scheduler

***(死锁)
开始:TID:48;SCTX:System.Web.AspNetSynchronizationContext;调度程序:System.Threading.Tasks.ThreadPoolTask​​Scheduler …

.net c# multithreading deadlock asynchronous

8
推荐指数
1
解决办法
1445
查看次数

使用ConfigureAwait(false)进行私有异步方法?

我有一个公共async方法,它调用3个不同的API来获取一些数据,然后将响应发布到下一个API.现在,根据斯蒂芬·克利里的文章,在这里,以避免死锁:

1.在"库"异步方法中,尽可能使用ConfigureAwait(false).
2.不要阻止任务; 一直使用async.

我想知道私有异步方法是否也是如此?ConfigureAwait(false)我在调用private异步方法时是否需要使用?所以沿途就行了

public async Task<int> ProcessAsync(ServiceTaskArgument arg)
{
    // do i need to use ConfigureAwait here while calling private async method?
    var response1 = await GetAPI1().ConfigureAwait(false);

    // do i need to use ConfigureAwait here while calling private async method?
    var response2= await PostAPI2(response1).ConfigureAwait(false);

    // do i need to use ConfigureAwait here while calling private async method?
    await PostAPI3(response2).ConfigureAwait(false);

    return 1;
}

private async Task<string> GetAPI1()
{
    var httpResponse = …
Run Code Online (Sandbox Code Playgroud)

c# async-await

2
推荐指数
1
解决办法
946
查看次数

为什么同步方式访问Task.Result不会出现死锁?

众所周知,在UI线程中访问Task的Result属性,同步模式会发生死锁。

理论上,后续代码会死锁,但不会。你能解释一下为什么吗?

// My "library" method.
public static async Task<JObject> GetJsonAsync(Uri uri)
{
  using (var client = new HttpClient())
  {
    var jsonString = await client.GetStringAsync(uri);
    return JObject.Parse(jsonString);
  }
}

//MVC action
public ActionResult Index()
{
      var result = System.Threading.Tasks.Task.Run(async () => await GetJsonAsync(...)).Result; // deadlock is expectation but not :(

    ...
}
Run Code Online (Sandbox Code Playgroud)

我认为这在某种程度上System.Threading.Tasks.Task.Run(async () => await GetJsonAsync(...)).Result是相似的,但事实并非如此。GetJsonAsync(...).Result

GetJsonAsync(...)).Result会陷入僵局,但System.Threading.Tasks.Task.Run(async () => await GetJsonAsync(...)).Result事实并非如此。

c# deadlock async-await

1
推荐指数
1
解决办法
564
查看次数