首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >为什么默认TaskScheduler是TaskScheduler.Current?

为什么默认TaskScheduler是TaskScheduler.Current?
EN

Stack Overflow用户
提问于 2011-07-23 21:34:07
回答 4查看 20.6K关注 0票数 68

任务并行库很棒,我在过去的几个月里用了很多。然而,有一件事真的困扰着我:TaskScheduler.Current是默认的任务调度程序,而不是TaskScheduler.Default。乍一看,这在文档和示例中都是不明显的。

Current可能会导致细微的错误,因为它的行为会根据您是否在另一个任务中而发生变化。这是不容易确定的。

假设我正在编写一个异步方法库,使用基于事件的标准异步模式在原始同步上下文中发出完成信号,就像XxxAsync方法在.NET框架中所做的那样(例如DownloadFileAsync)。我决定使用任务并行库来实现,因为使用以下代码实现此行为非常容易:

public class MyLibrary
{
    public event EventHandler SomeOperationCompleted;

    private void OnSomeOperationCompleted()
    {
        SomeOperationCompleted?.Invoke(this, EventArgs.Empty);
    }

    public void DoSomeOperationAsync()
    {
        Task.Factory.StartNew(() =>
        {
            Thread.Sleep(1000); // simulate a long operation
        }, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default)
        .ContinueWith(t =>
        {
            OnSomeOperationCompleted(); // trigger the event
        }, TaskScheduler.FromCurrentSynchronizationContext());
    }
}

到目前为止,一切都运行良好。现在,让我们通过在WPF或WinForms应用程序中单击按钮来调用这个库:

private void Button_OnClick(object sender, EventArgs args)
{
    var myLibrary = new MyLibrary();
    myLibrary.SomeOperationCompleted += (s, e) => DoSomethingElse();
    myLibrary.DoSomeOperationAsync(); // call that triggers the event asynchronously
}

private void DoSomethingElse() // the event handler
{
    //...
    Task.Factory.StartNew(() => Thread.Sleep(5000)); // simulate a long operation
    //...
}

在这里,编写库调用的人选择在操作完成时启动一个新的Task。没什么特别的。他或她遵循web上随处可见的示例,简单地使用Task.Factory.StartNew,而不指定TaskScheduler (而且在第二个参数中指定它也不容易重载)。单独调用DoSomethingElse方法时,它工作得很好,但一旦它被事件调用,UI就会冻结,因为TaskFactory.Current将重用我的库context中的同步上下文任务调度程序。

找出这一点可能需要一些时间,特别是如果第二个任务调用隐藏在一些复杂的调用堆栈中。当然,一旦您了解了所有操作的工作原理,这里的修复方法就很简单:总是为您希望在线程池上运行的任何操作指定TaskScheduler.Default。然而,也许第二个任务是由另一个外部库启动的,它不知道这种行为,并且在没有特定调度程序的情况下天真地使用StartNew。我希望这种情况很常见。

考虑到这一点,我无法理解编写第三方公共关系的团队为什么选择使用TaskScheduler.Current而不是TaskScheduler.Default作为默认设置:

  • 这一点都不明显,Default不是默认的!Current使用的真正的任务调度程序
  • 依赖于调用堆栈!使用StartNew指定任务调度器很难使用这个behavior.
  • It's维护不变量,因为您必须首先指定任务创建选项和取消令牌,这会导致很长的、可读性较差的行。这可以通过编写扩展方法或创建使用Default.
  • Capturing的TaskFactory来缓解。调用堆栈有额外的性能成本。
  • 当我真的希望一个任务依赖于另一个正在运行的父任务时,我更喜欢显式指定它以简化代码阅读,而不是依赖调用堆栈魔术。

我知道这个问题可能听起来很主观,但我找不到一个很好的客观论据来解释为什么这种行为是这样的。我确信我在这里遗漏了什么:这就是我求助于你的原因。

EN

回答 4

Stack Overflow用户

发布于 2011-07-23 22:22:23

我认为目前的行为是有道理的。如果我创建了自己的任务调度程序,并启动了一些启动其他任务的任务,我可能希望所有任务都使用我创建的调度程序。

我同意,从UI线程启动任务有时使用默认调度程序,有时不使用,这很奇怪。但我不知道如果我在设计它,我该如何让它变得更好。

关于你的具体问题:

  • 我认为在指定的调度程序上启动新任务的最简单方法是new Task(lambda).Start(scheduler)。这样做的缺点是,如果任务返回某些内容,则必须指定类型参数。TaskFactory.Create可以为你推断类型。
  • 你可以用Dispatcher.Invoke()代替
票数 19
EN

Stack Overflow用户

发布于 2011-11-20 21:39:40

编辑以下内容仅解决Task.Factory.StartNew使用的调度程序的问题。

但是,Task.ContinueWith有一个硬编码的TaskScheduler.Current。/EDIT

首先,有一个简单的解决方案--参见这篇文章的底部。

这个问题背后的原因很简单:不仅有一个默认的任务调度器(TaskScheduler.Default),而且还有一个TaskFactory的默认任务调度器(TaskFactory.Scheduler)。这个默认调度程序可以在创建时在TaskFactory的构造函数中指定。

但是,Task.Factory背后的TaskFactory是这样创建的:

s_factory = new TaskFactory();

正如您所看到的,没有指定TaskSchedulernull被用作默认构造函数-最好是TaskScheduler.Default (文档中指出使用"Current“具有相同的后果)。

这再次导致了TaskFactory.DefaultScheduler (私有成员)的实现:

private TaskScheduler DefaultScheduler 
{ 
   get
   { 
      if (m_defaultScheduler == null) return TaskScheduler.Current;
      else return m_defaultScheduler;
   }
}

在这里,您应该看到能够识别此行为的原因:由于Task.Factory没有默认的任务调度程序,因此将使用当前的任务调度程序。

那么,当当前没有任务正在执行时(即我们没有当前的TaskScheduler),我们为什么不运行NullReferenceExceptions呢?

原因很简单:

public static TaskScheduler Current
{
    get
    {
        Task internalCurrent = Task.InternalCurrent;
        if (internalCurrent != null)
        {
            return internalCurrent.ExecutingTaskScheduler;
        }
        return Default;
    }
}

TaskScheduler.Current默认为TaskScheduler.Default

我会称这是一个非常不幸的实现。

但是,有一个简单的修复方法:我们可以简单地将Task.Factory的默认TaskScheduler设置为TaskScheduler.Default

TaskFactory factory = Task.Factory;
factory.GetType().InvokeMember("m_defaultScheduler", BindingFlags.SetField | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly, null, factory, new object[] { TaskScheduler.Default });

我希望我能帮助我的回应,尽管现在已经很晚了:-)

票数 8
EN

Stack Overflow用户

发布于 2013-08-28 15:40:08

代替Task.Factory.StartNew()

考虑使用:Task.Run()

这将始终在线程池线程上执行。我刚刚遇到了问题中描述的相同问题,我认为这是一个很好的处理方法。

查看此博客条目:http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx

票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/6800705

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档