Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/google-chrome/4.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# 重新访问Task.ConfigureAwait(continueOnCapturedContext:false)_C#_.net_Task Parallel Library_Async Await - Fatal编程技术网

C# 重新访问Task.ConfigureAwait(continueOnCapturedContext:false)

C# 重新访问Task.ConfigureAwait(continueOnCapturedContext:false),c#,.net,task-parallel-library,async-await,C#,.net,Task Parallel Library,Async Await,太长,无法读取。使用Task.ConfigureAwait(continueOnCapturedContext:false)可能会引入冗余线程切换。我正在寻找一个一致的解决方案 长版本。ConfigureAwait(false)背后的主要设计目标是减少冗余的SynchronizationContext。如果可能,发布await的后续回调。这通常意味着更少的线程切换和更少的UI线程工作。然而,这并不总是它的工作原理 例如,有一个第三方库实现了SomeAsyncApiAPI。请注意,由于某些原因,C

太长,无法读取。使用
Task.ConfigureAwait(continueOnCapturedContext:false)
可能会引入冗余线程切换。我正在寻找一个一致的解决方案

长版本。ConfigureAwait(false)背后的主要设计目标是减少冗余的
SynchronizationContext。如果可能,发布
await
的后续回调。这通常意味着更少的线程切换和更少的UI线程工作。然而,这并不总是它的工作原理

例如,有一个第三方库实现了
SomeAsyncApi
API。请注意,由于某些原因,
ConfigureAwait(false)
未在此库中的任何位置使用:

// some library, SomeClass class
public static async Task<int> SomeAsyncApi()
{
    TaskExt.Log("X1");

    // await Task.Delay(1000) without ConfigureAwait(false);
    // WithCompletionLog only shows the actual Task.Delay completion thread
    // and doesn't change the awaiter behavior

    await Task.Delay(1000).WithCompletionLog(step: "X1.5");

    TaskExt.Log("X2");

    return 42;
}

// logging helpers
public static partial class TaskExt
{
    public static void Log(string step)
    {
        Debug.WriteLine(new { step, thread = Environment.CurrentManagedThreadId });
    }

    public static Task WithCompletionLog(this Task anteTask, string step)
    {
        return anteTask.ContinueWith(
            _ => Log(step),
            CancellationToken.None,
            TaskContinuationOptions.ExecuteSynchronously,
            TaskScheduler.Default);
    }
}
输出:

{ step = A1, thread = 9 } { step = B1, thread = 9 } { step = X1, thread = 9 } { step = X1.5, thread = 11 } { step = X2, thread = 9 } { step = B2, thread = 11 } { step = A2, thread = 9 } 然而,
MethodAsync
的作者使用了
ConfigureAwait(false)
,出于良好的意图和遵循,她对
SomeAsyncApi
的内部实现一无所知如果一直使用
ConfigureAwait(false)
(即在
SomeAsyncApi
内部),这不会是一个问题,但这超出了她的控制范围

这就是
windowsformsssynchronizationcontext
(或
dispatcherssynchronizationcontext
)的情况,我们可能根本不关心额外的线程切换。但是,在ASP.NET中也可能发生类似的情况,其实质是:

Task newTask = _lastScheduledTask.ContinueWith(_ => SafeWrapCallback(action));
_lastScheduledTask = newTask;
整个过程看起来可能是人为的,但我确实看到了很多这样的生产代码,包括客户端和服务器端。我遇到的另一个有问题的模式是:
await TaskCompletionSource.Task.ConfigureAwait(false)
,其中
SetResult
在为前者捕获的同步上下文上被调用。继续操作再次被冗余地推送到
ThreadPool
。这种模式背后的理由是“它有助于避免死锁”

问题:根据所描述的
ConfigureAwait(false)
行为,我正在寻找一种优雅的方式来使用
async/await
,同时尽量减少冗余线程/上下文切换。理想情况下,可以使用现有的第三方库

到目前为止,我所看到的

  • 使用
    Task.Run卸载
    async
    lambda并不理想,因为它至少引入了一个额外的线程开关(尽管它可能会节省许多其他线程开关):

  • 另一个黑客解决方案可能是暂时从当前线程中删除同步上下文,这样它就不会被内部调用链中的任何后续等待捕获(我之前提到过它):

如果还有其他想法,我将不胜感激

更新,以解决:

我会说这是另一种方式,因为正如斯蒂芬在书中所说 他的回答是“配置等待(错误)的目的不是诱导 线程切换(如有必要),而是为了防止代码过多 在特定环境下运行。”您不同意,并且 是您的兼容的根

由于您的答案已被编辑,为了清楚起见,我不同意:

ConfigureWait(false)的目标是尽可能减少工作量 “特殊”(例如UI)线程需要处理,而不管线程是什么 它需要的开关

我也不同意你对这一说法的看法。我将向您介绍主要来源Stephen Toub的:

避免不必要的编组

如果可能的话,请确保调用的是异步实现 不需要阻塞线程来完成操作 (这样,您就可以使用普通的阻塞机制来等待 同步进行异步工作,以便在其他位置完成)。在 在异步/等待的情况下,这通常意味着确保 在您正在调用的异步实现中,您正在使用 在所有等待点上配置等待(false);这将阻止等待 从尝试封送回当前SynchronizationContext。作为 作为库实现者,始终使用它是最佳实践 在所有等待上配置等待(false),除非您有 不同意的具体理由;这不仅有助于避免这些问题 各种死锁问题,但也会影响性能,因为它可以避免 不必要的编组成本。

它确实指出,目标是为了提高性能,避免不必要的编组成本。线程切换(在
ExecutionContext
中流动)是一个巨大的封送成本

现在,它没有说目标是减少在“特殊”线程或上下文上完成的工作量

虽然这对UI线程来说可能有一定意义,但我仍然不认为这是
ConfigureAwait
背后的主要目标。还有其他更结构化的方法来最小化UI线程上的工作,比如使用大块的
wait Task.Run(work)

此外,将
AspNetSynchronizationContext
上的工作最小化是毫无意义的,这与UI线程不同,AspNetSynchronizationContext本身从一个线程流向另一个线程。恰恰相反,一旦你在代码> ASPNETSimultIsActudio<<代码>中,你就要尽可能多地工作,以避免在处理HTTP请求过程中不必要的切换。尽管如此,在ASP.NET中使用
ConfigureAwait(false)
仍然非常有意义:如果使用正确,它会再次减少服务器端线程切换

ConfigureAwait(false)背后的主要设计目标是减少冗余的SynchronizationContext。尽可能对await进行后续回调。这通常意味着更少的线程切换和更少的UI线程工作

我不同意你的前提<代码>配置等待(false)
目标是尽可能减少 { step = A1, thread = 9 } { step = B1, thread = 9 } { step = X1, thread = 9 } { step = X1.5, thread = 11 } { step = X2, thread = 9 } { step = B2, thread = 9 } { step = A2, thread = 9 }
Task newTask = _lastScheduledTask.ContinueWith(_ => SafeWrapCallback(action));
_lastScheduledTask = newTask;
await Task.Run(() => SomeAsyncApi()).ConfigureAwait(false);
async Task MethodAsync()
{
    TaskExt.Log("B1");
    await TaskExt.WithNoContext(() => SomeAsyncApi()).ConfigureAwait(false);
    TaskExt.Log("B2");
}
{ step = A1, thread = 8 } { step = B1, thread = 8 } { step = X1, thread = 8 } { step = X1.5, thread = 10 } { step = X2, thread = 10 } { step = B2, thread = 10 } { step = A2, thread = 8 }
public static Task<TResult> WithNoContext<TResult>(Func<Task<TResult>> func)
{
    Task<TResult> task;
    var sc = SynchronizationContext.Current;
    try
    {
        SynchronizationContext.SetSynchronizationContext(null);
        // do not await the task here, so the SC is restored right after
        // the execution point hits the first await inside func
        task = func();
    }
    finally
    {
        SynchronizationContext.SetSynchronizationContext(sc);
    }
    return task;
}
// task = func();
var task2 = new Task<Task<TResult>>(() => func());
task2.RunSynchronously(TaskScheduler.Default); 
task = task2.Unwrap();
private async void Button_Click(object sender, RoutedEventArgs e)
{
    TaskExt.Log("A1");
    await AnotherClass.MethodAsync().ConfigureAwait(false);
    TaskExt.Log("A2");
}

public class AnotherClass
{
    public static async Task MethodAsync()
    {
        TaskExt.Log("B1");
        await SomeClass.SomeAsyncApi().ConfigureAwait(false);
        TaskExt.Log("B2");
    }
}

public class SomeClass
{
    public static async Task<int> SomeAsyncApi()
    {
        TaskExt.Log("X1");
        await Task.Delay(1000).WithCompletionLog(step: "X1.5").ConfigureAwait(false);
        TaskExt.Log("X2");
        return 42;
    }
}
{ step = A1, thread = 9 }
{ step = B1, thread = 9 }
{ step = X1, thread = 9 }
{ step = X1.5, thread = 11 }
{ step = X2, thread = 11 }
{ step = B2, thread = 11 }
{ step = A2, thread = 11 }
private async void Button_Click(object sender, RoutedEventArgs e)
{
    TaskExt.Log("A1");
    await Task.Run(() => AnotherClass.MethodAsync()).ConfigureAwait(false);
    TaskExt.Log("A2");
}
{ step = A1, thread = 9 }
{ step = B1, thread = 10 }
{ step = X1, thread = 10 }
{ step = X1.5, thread = 11 }
{ step = X2, thread = 11 }
{ step = B2, thread = 11 }
{ step = A2, thread = 11 }
public static ConfiguredTaskAwaitable2 ConfigureAwait2(this Task task,
    bool continueOnCapturedContext)
    => new ConfiguredTaskAwaitable2(task, continueOnCapturedContext);

public struct ConfiguredTaskAwaitable2 : INotifyCompletion
{
    private readonly Task _task;
    private readonly bool _continueOnCapturedContext;

    public ConfiguredTaskAwaitable2(Task task, bool continueOnCapturedContext)
    {
        _task = task; _continueOnCapturedContext = continueOnCapturedContext;
    }
    public ConfiguredTaskAwaitable2 GetAwaiter() => this;
    public bool IsCompleted { get { return _task.IsCompleted; } }
    public void GetResult() { _task.GetAwaiter().GetResult(); }
    public void OnCompleted(Action continuation)
    {
        var capturedContext = _continueOnCapturedContext ?
            SynchronizationContext.Current : null;
        _ = _task.ContinueWith(_ =>
        {
            if (capturedContext != null)
                capturedContext.Post(_ => continuation(), null);
            else
                continuation();
        }, default, TaskContinuationOptions.ExecuteSynchronously,
            TaskScheduler.Default);
    }
}