【问题标题】:Does TaskScheduler.Default not always guarantee the task will be executed on a pool thread?TaskScheduler.Default 不总是保证任务将在池线程上执行吗?
【发布时间】:2014-08-03 17:53:00
【问题描述】:

TaskScheduler.Default是否总是保证任务将在池线程上执行?

在修复错误时,我发现至少有一种情况没有修复。它可以像这样复制(由真实代码制作的人为示例):

var tcs = new TaskCompletionSource<bool>();
var sc = SynchronizationContext.Current;
sc.Post(_ => tcs.SetResult(true), null);
await tcs.Task.ContinueWith(_ =>
    {
        // breaks here
        Debug.Assert(Thread.CurrentThread.IsThreadPoolThread);
    }, 
    CancellationToken.None, 
    TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);

还有其他情况吗?

另外,有没有一种优雅的方法来确保ContinueWith 操作同步执行,如果前面的任务已经在池线程上完成,或者排队到线程池中(我知道我可以使用QueueUserWorkItem in ContinueWith 行动,但我不喜欢它)。

已编辑,我想我可以实现自己的 TaskScheduler 并检查我是否已经在 TryExecuteTaskInline 内的线程池线程上,以控制它。

【问题讨论】:

  • “...ContinueWith 动作同步执行,如果前面的任务已在池线程上完成”究竟是什么意思? 同步在这里应该是什么意思?如果先行项在池线程上运行,这怎么可能有意义?
  • 另外,你说这个repro case 不在池线程上执行,那么它在哪个线程上执行?此代码 sn-p 是否是当前同步上下文是 Winforms 或 WPF 调度程序的 UI 代码的一部分? TaskSchedulerSynchronizationContext 有古怪的交互,这就是为什么你通常使用 TaskScheduler.FromCurrentSynchronizationContext 而不是 TaskScheduler.Default
  • @Aaronaught,实际代码用TaskCompletionSource 包装了第 3 方代码事件。该事件可以在任何线程上触发,包括具有同步上下文的线程。我绝对不想要TaskScheduler.FromCurrentSynchronizationContext。我想处理线程池上TaskCompletionSource.Task 的完成。如果 TaskContinuationOptions.ExecuteSynchronously 已经在线程池中,我想使用它作为优化,以避免不必要的线程切换。
  • 请注意,ExecuteSynchronously 从不保证在同一个线程上同步运行。这是一个提示。
  • @usr,谢谢你的好点。我刚读到什么时候不是:blogs.msdn.com/b/pfxteam/archive/2012/02/07/10265067.aspx

标签: c# .net task-parallel-library async-await task


【解决方案1】:

我认为发生这种情况的原因是因为您使用了TaskContinuationOptions.ExecuteSynchronously。来自doco:

ExecuteSynchronously 指定继续任务应该是 同步执行。指定此选项后,继续 将在导致先前任务的同一线程上运行 过渡到它的最终状态。如果前件已经完成 创建延续时,延续将在 创建延续的线程。只有非常短暂的运行 延续应该同步执行。

如果前面的任务在线程池线程以外的线程上完成,那么延续也将在该线程上运行。所以我想在这种情况下不会发生调度。另一种情况可能是延续任务已经完成,它也将同步运行。

更新

为了实现您在问题的第二部分中提出的问题,我认为您需要一个自定义等待者。有关更多详细信息,请参阅this article,但类似的方法可能对您有用:

public static class Extensions
{
    public static ThreadPoolTaskAwaiter WithThreadPool(this Task task)
    {
        return new ThreadPoolTaskAwaiter(task);
    }

    public class ThreadPoolTaskAwaiter : INotifyCompletion
    {
        private readonly TaskAwaiter m_awaiter;

        public ThreadPoolTaskAwaiter(Task task)
        {
            if (task == null) throw new ArgumentNullException("task");
            m_awaiter = task.GetAwaiter();
        }

        public ThreadPoolTaskAwaiter GetAwaiter() { return this; }

        public bool IsCompleted { get { return m_awaiter.IsCompleted; } }

        public void OnCompleted(Action continuation)
        {
            if (Thread.CurrentThread.IsThreadPoolThread)
            {
                continuation();
            }
            else
            {
                Task.Run(continuation);
            }                
        }

        public void GetResult()
        {
            m_awaiter.GetResult();
        }
    }
}

然后你这样使用它:

public static async Task Execute()
{          
    await Task.Delay(500).WithThreadPool();

    // does not break here
    if (!Thread.CurrentThread.IsThreadPoolThread)
    {
        Debugger.Break();
    }
}

【讨论】:

  • 我知道TaskContinuationOptions.ExecuteSynchronously 是原因,但我仍在寻找帖子最后一段中问题的答案。
  • 我想我可以实现我自己的 TaskScheduler 并检查我是否已经在 TryExecuteTaskInline 内的线程池线程上,以控制它。
  • 您也可以使用自定义等待器。查看我的更新。
猜你喜欢
  • 2016-03-13
  • 2011-11-03
  • 1970-01-01
  • 2014-05-23
  • 2019-10-08
  • 1970-01-01
  • 2013-07-22
  • 2016-12-10
  • 2015-06-26
相关资源
最近更新 更多