代码之家  ›  专栏  ›  技术社区  ›  StriplingWarrior

何时应使用taskscheduler.current作为参数调用task.continue?

  •  12
  • StriplingWarrior  · 技术社区  · 7 年前

    我们正在使用 this code snippet 从stackoverflow生成一个任务,该任务在任务集合的第一个成功完成时立即完成。由于它的执行是非线性的, async/await 不是真正可行的,所以这段代码使用 ContinueWith() 相反。但它没有指定taskscheduler, number of sources 上面提到的可能很危险,因为它使用 TaskScheduler.Current 当大多数开发人员通常期望 TaskScheduler.Default 持续性行为。

    普遍的看法似乎是,您应该始终将显式任务调度程序传递到continue中。但是,我还没有看到一个明确的解释,说明什么时候不同的任务调度程序最合适。

    一个最好通过的案例的具体例子是什么? 任务计划程序。当前 进入之内 继续() ,与 任务计划程序。默认值 是吗?做这个决定要遵循经验法则吗?

    对于上下文,这里是我所指的代码片段:

    public static Task<T> FirstSuccessfulTask<T>(IEnumerable<Task<T>> tasks)
    {
        var taskList = tasks.ToList();
        var tcs = new TaskCompletionSource<T>();
        int remainingTasks = taskList.Count;
        foreach(var task in taskList)
        {
            task.ContinueWith(t =>
                if(task.Status == TaskStatus.RanToCompletion)
                    tcs.TrySetResult(t.Result));
                else
                    if(Interlocked.Decrement(ref remainingTasks) == 0)
                        tcs.SetException(new AggregateException(
                            tasks.SelectMany(t => t.Exception.InnerExceptions));
        }
        return tcs.Task;
    }
    
    4 回复  |  直到 7 年前
        1
  •  3
  •   Leonid Vasilev    7 年前

    可能需要选择一个适合执行委托实例执行的操作的任务计划程序。

    考虑以下示例:

    Task ContinueWithUnknownAction(Task task, Action<Task> actionOfTheUnknownNature)
    {
        // We know nothing about what the action do, so we decide to respect environment
        // in which current function is called
        return task.ContinueWith(actionOfTheUnknownNature, TaskScheduler.Current);
    }
    
    int count;
    Task ContinueWithKnownAction(Task task)
    {
        // We fully control a continuation action and we know that it can be safely 
        // executed by thread pool thread.
        return task.ContinueWith(t => Interlocked.Increment(ref count), TaskScheduler.Default);
    }
    
    Func<int> cpuHeavyCalculation = () => 0;
    Action<Task> printCalculationResultToUI = task => { };
    void OnUserAction()
    {
        // Assert that SynchronizationContext.Current is not null.
        // We know that continuation will modify an UI, and it can be safely executed 
        // only on an UI thread.
        Task.Run(cpuHeavyCalculation)
            .ContinueWith(printCalculationResultToUI, TaskScheduler.FromCurrentSynchronizationContext());
    }
    

    你的 FirstSuccessfulTask() 可能是你可以使用的例子 TaskScheduler.Default ,因为可以在线程池上安全地执行连续委托实例。

    还可以使用自定义任务调度程序在库中实现自定义调度逻辑。例如,请参见 Scheduler 奥尔良框架网站页面。

    有关详细信息,请检查:

        2
  •  9
  •   Hans Passant    7 年前

    我得说一点,这让太多的程序员陷入麻烦。每一个旨在使线程看起来简单的编程辅助工具都会产生五个程序员没有机会调试的新问题。

    后台工作人员是第一个,一个谦虚和明智的尝试,以隐藏并发症。但是没有人意识到工作线程在线程池上运行,所以不应该占用自己的I/O。每个人都会犯错,没有多少人注意到。而且忘记检查runworkercompleted事件中的e.error,在线程代码中隐藏异常是wrappers的一个普遍问题。

    异步/等待模式是最新的,它使 真正地 别紧张。但是它的构图非常糟糕,异步海龟一直到main()为止。他们最终不得不在C版本7.2中解决这个问题,因为每个人都被困在上面了。但不能修复库中的DraseCuffulaWaWIT()问题。它完全倾向于图书馆的作者知道他们在做什么,值得注意的是,他们中的许多人为微软工作,并修补winrt。

    任务类弥补了两者之间的差距,其设计目标是 非常 镇定。好计划,他们无法预测程序员将如何使用它。但同时也是一种责任,它鼓励程序员继续进行一场将任务粘合在一起的风暴。即使这样做没有意义,因为这些任务只是按顺序运行。值得注意的是,他们甚至添加了一个优化,以确保continuation在同一个线程上运行,从而避免上下文切换开销。很好的计划,但却造成了这个网站命名的不可调试的问题。

    所以是的,你看到的建议很好。任务是有用的 异步性 是的。当服务进入“云”并且延迟成为一个你不能再忽视的细节时,你必须处理的一个常见问题。如果您继续使用()这种代码,那么您总是关心执行继续的特定线程。由taskscheduler提供,它不是fromcurrentsynchronizationcontext()提供的可能性很低。异步/等待就是这样发生的。

        3
  •  3
  •   Ben Hall    7 年前

    如果当前任务是子任务,则使用 TaskScheduler.Current 将意味着调度程序将是它所在的任务的调度对象;如果不在另一个任务中, 任务计划程序。当前 TaskScheduler.Default 因此使用线程池。

    如果你使用 任务计划程序。默认值 ,然后它将始终转到线程池。

    你唯一会用的理由 任务计划程序。当前 以下内容:

    为了避免默认的调度程序问题,您应该始终传递 明确的 TaskScheduler Task.ContinueWith Task.Factory.StartNew 是的。

    斯蒂芬·克利里的帖子 ContinueWith is Dangerous, Too 是的。

    Stephen Toub对他的 MSDN blog 是的。

        4
  •  2
  •   kuskmen    7 年前

    我当然不认为我能提供防弹答案,但我会给我5美分。

    在这种情况下,最好将taskscheduler.current传递到continueWith()中,而不是将taskscheduler.default传递给continueWith()?

    想象一下,您正在开发一些web api,webserver自然地使其成为多线程的。因此,您需要破坏并行性,因为您不想使用Web服务器的所有资源,但同时您希望加快处理时间,因此您决定使用较低的并发级别来制作自定义任务计划程序,因为为什么不这样做。

    现在您的api需要查询一些数据库并对结果进行排序,但是这些结果有数百万个,所以您决定通过合并排序(divide and conquer)来完成,然后您需要此算法的所有子任务都与您的自定义任务调度程序兼容。( TaskScheduler.Current )因为否则,您将占用算法的所有资源,您的web服务器线程池将耗尽。

    何时使用taskscheduler.current、taskscheduler.default、taskscheduler.fromCurrentSynchronizationContext()或其他任务计划程序

    • taskscheduler.fromCurrentSynchronizationContext()-特定于wpf, 窗体应用程序UI线程上下文,基本上当 在将一些工作卸载到 非UI线程

    示例取自 here

    private void button_Click(…) 
    { 
        … // #1 on the UI thread 
        Task.Factory.StartNew(() => 
        { 
            … // #2 long-running work, so offloaded to non-UI thread 
        }).ContinueWith(t => 
        { 
            … // #3 back on the UI thread 
        }, TaskScheduler.FromCurrentSynchronizationContext()); 
    }
    
    • 默认情况下-几乎所有你没有任何具体要求的时候,都会有一些边缘案例需要整理。
    • 当前的-我想我已经给出了上面的一个通用示例,但是一般来说,当您有定制的调度器或者显式地传递 TaskScheduler.FromCurrentSynchronizationContext() TaskFactory Task.StartNew 方法,然后使用连续任务或内部任务(在imo中非常罕见)。