【问题标题】:How to substitute synchronization context / task scheduler to another one inside TaskCompletionSource.Task for ConfigureAwait(false)? [duplicate]如何将同步上下文/任务调度程序替换为 TaskCompletionSource.Task 中的另一个同步上下文/任务调度程序以获取 ConfigureAwait(false)? [复制]
【发布时间】:2016-04-12 00:38:44
【问题描述】:

假设我创建了一个包含此类方法的库:

Task MyLibraryMethodAsync()
{
    var taskCompletionSource = new TaskCompletionSource<object>();

    Action myWorkItem =
        () =>
        {
            // Simulate some work.
            // Actual work items depend on input params.
            Thread.Sleep(TimeSpan.FromSeconds(1));

            taskCompletionSource.SetResult(null);
        };

    // The next two lines is simplification for demonstration.
    // I do not have access to the workerThread - it is created
    // and managed for me by another lib.
    // All I can do - is to post some short work items to it.
    var workerThread = new Thread(new ThreadStart(myWorkItem));
    workerThread.Start();

    return taskCompletionSource.Task;
}

我的库的任何用户都可以像这样调用MyLibraryMethodAsync

await MyLibraryMethodAsync().ConfigureAwait(false);
VeryLongRunningMethod();
void VeryLongRunningMethod()
{
    Thread.Sleep(TimeSpan.FromHours(1));
}

问题来了——VeryLongRunningMethod 将在taskCompletionSource.SetResult(null) 调用中执行,因此它将长时间阻塞workerThread,这是不希望的行为,因为workerThread 旨在运行较小部分代码(工作项)。

如何将上下文/调度程序替换为返回的任务中的线程池,使await x.ConfigureAwait(false)线程池上继续,但不在workerThread 上?

我目前找到的解决方案是

Task MyLibraryMethodAsync()
{
    // ...

    return taskCompletionSource.Task
        .ContinueWith(x => x.Result, TaskScheduler.Default);
}

但是,我不喜欢它,因为它会产生开销。 可能存在更优雅的解决方案吗?

【问题讨论】:

    标签: c# multithreading async-await task-parallel-library taskcompletionsource


    【解决方案1】:

    不确定我是否理解正确,但您可以显式创建后台任务以避免阻塞:

    await MyLibraryMethodAsync().ConfigureAwait(false);
    await Task.Run(() => VeryLongRunningMethod());
    

    您甚至可以省略 ConfigureAwait:

    await MyLibraryMethodAsync()
    await Task.Run(() => VeryLongRunningMethod());
    

    编辑:根据您的评论:如果您作为库的作者想要防止阻塞线程,您可以使用:

    Task.Run(() => taskCompletionSource.SetResult(null));
    

    【讨论】:

    • 从用户的角度来看,你是对的。但我是 lib 开发人员,我需要确保 lib 用户无论如何都不会阻塞工作线程。
    • 查看编辑 - 使用 Task.Run(() => taskCompletionSource.SetResult(null));设置结果?
    • 这会产生与我当前解决方案相同的开销。无论如何,Servy 解决了我的问题。
    【解决方案2】:

    从 .NET 4.6 开始,TaskCreationOptions 中有一个名为 RunContinuationsAsynchronously 的选项,它完全符合您的要求,它确保所有延续都异步运行,而不是在设置结果时同步运行。 TaskCompletionSource 在其构造函数中有一个可选的TaskCreationOption 参数供您提供该选项。

    如果您使用的是早期版本的 .NET,则需要进行效率较低的 hack,例如添加另一个延续,如您所展示的,或者在线程池线程中显式设置结果,而不是通过回调动作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-06
      • 1970-01-01
      • 2020-09-15
      • 1970-01-01
      相关资源
      最近更新 更多