我有方法:
private static void Method()
{
Console.WriteLine("Method() started");
for (var i = 0; i < 20; i++)
{
Console.WriteLine("Method() Counter = " + i);
Thread.Sleep(500);
}
Console.WriteLine("Method() finished");
}
我想在一个新任务中开始这个方法。
我可以像这样开始新的任务
var task = Task.Factory.StartNew(new Action(Method));
或者这个
var task = Task.Run(new Action(Method));
但是Task.Run()和Task.Factory.StartNew()之间有什么区别吗?它们都在使用ThreadPool,并在创建Task实例后立即启动Method()。什么时候用第一种,什么时候用第二种?
人们已经提到过
Task.Run(A);
等于
Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
但是没有人提到这一点
Task.Factory.StartNew(A);
等价于:
Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Current);
正如您所看到的,Task的两个参数是不同的。运行Task.Factory.StartNew:
TaskCreationOptions任务。Run使用taskcreateoptions。DenyChildAttach,这意味着子任务不能附加到父任务,考虑如下:
var parentTask = Task.Run(() => . var
{
var childTask = new Task(() => . var
{
thread . sleep (10000);
控制台。WriteLine(“子任务完成。”);
}, TaskCreationOptions.AttachedToParent);
childTask.Start ();
控制台。WriteLine(“父任务完成。”);
});
parentTask.Wait ();
控制台。WriteLine(“主线程完成。”);
当我们调用parentTask.Wait()时,即使我们指定了TaskCreationOptions,也不会等待childTask。attachedtopparent,这是因为TaskCreationOptions。DenyChildAttach禁止儿童附加到它。如果你用Task. factory . startnew而不是Task. factory . startnew运行相同的代码。运行时,parentTask.Wait()将等待childTask,因为Task.Factory.StartNew使用TaskCreationOptions。没有一个
TaskScheduler - Task.Run uses TaskScheduler.Default which means that the default task scheduler (the one that runs tasks on Thread Pool) will always be used to run tasks. Task.Factory.StartNew on the other hand uses TaskScheduler.Current which means scheduler of the current thread, it might be TaskScheduler.Default but not always. In fact when developing Winforms or WPF applications it is required to update UI from the current thread, to do this people use TaskScheduler.FromCurrentSynchronizationContext() task scheduler, if you unintentionally create another long running task inside task that used TaskScheduler.FromCurrentSynchronizationContext() scheduler the UI will be frozen. A more detailed explanation of this can be found here
所以一般来说,如果你不使用嵌套的子任务,并且总是希望你的任务在线程池上执行,最好使用任务。运行,除非您有一些更复杂的场景。
根据Stephen Cleary的这篇文章,Task.Factory.StartNew()是危险的:
I see a lot of code on blogs and in SO questions that use Task.Factory.StartNew to spin up work on a background thread. Stephen Toub has an excellent blog article that explains why Task.Run is better than Task.Factory.StartNew, but I think a lot of people just haven’t read it (or don’t understand it). So, I’ve taken the same arguments, added some more forceful language, and we’ll see how this goes. :)
StartNew does offer many more options than Task.Run, but it is quite dangerous, as we’ll see. You should prefer Task.Run over Task.Factory.StartNew in async code.
以下是真正的原因:
Does not understand async delegates. This is actually the same as
point 1 in the reasons why you would want to use StartNew. The problem
is that when you pass an async delegate to StartNew, it’s natural to
assume that the returned task represents that delegate. However, since
StartNew does not understand async delegates, what that task actually
represents is just the beginning of that delegate. This is one of the
first pitfalls that coders encounter when using StartNew in async
code.
Confusing default scheduler. OK, trick question time: in the
code below, what thread does the method “A” run on?
Task.Factory.StartNew(A);
private static void A() { }
你知道这是个陷阱问题,对吧?如果你回答“一根线”
pool thread ",很抱歉,这是错误的。A会继续
不管TaskScheduler当前正在执行什么!
所以这意味着如果一个操作完成,它可能会运行在UI线程上,并且由于一个延续,它会封送回UI线程,Stephen Cleary在他的帖子中解释得更详细。
在我的例子中,我试图在加载一个视图的数据网格时在后台运行任务,同时还显示一个繁忙的动画。当使用Task.Factory.StartNew()时,繁忙动画没有显示,但当我切换到Task.Run()时,动画显示正常。
详情请见https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html
这个任务。Run在新的。net框架版本中被引入,推荐使用。
从. net Framework 4.5开始,Task。运行方法是
推荐的启动计算绑定任务的方法。使用StartNew
方法仅在需要对长时间运行的
的计算任务。
Task. factory . startnew有更多的选项,Task。Run是一个简写:
Run方法提供了一组重载,使其易于启动
使用默认值的任务。它是一种轻量级的替代
StartNew过载。
这里我指的是技术上的捷径:
public static Task Run(Action action)
{
return Task.InternalStartNew(null, action, null, default(CancellationToken), TaskScheduler.Default,
TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None, ref stackMark);
}
除了Task.Run()是Task.Factory.StartNew()的简写之外,在同步委托和异步委托的情况下,它们的行为有细微的区别。
假设有以下两种方法:
public async Task<int> GetIntAsync()
{
return Task.FromResult(1);
}
public int GetInt()
{
return 1;
}
现在考虑下面的代码。
var sync1 = Task.Run(() => GetInt());
var sync2 = Task.Factory.StartNew(() => GetInt());
这里sync1和sync2的类型都是Task<int>
然而,不同之处在于异步方法。
var async1 = Task.Run(() => GetIntAsync());
var async2 = Task.Factory.StartNew(() => GetIntAsync());
在这个场景中,async1的类型是Task<int>,而async2的类型是Task<Task<int>>
人们已经提到过
Task.Run(A);
等于
Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
但是没有人提到这一点
Task.Factory.StartNew(A);
等价于:
Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Current);
正如您所看到的,Task的两个参数是不同的。运行Task.Factory.StartNew:
TaskCreationOptions任务。Run使用taskcreateoptions。DenyChildAttach,这意味着子任务不能附加到父任务,考虑如下:
var parentTask = Task.Run(() => . var
{
var childTask = new Task(() => . var
{
thread . sleep (10000);
控制台。WriteLine(“子任务完成。”);
}, TaskCreationOptions.AttachedToParent);
childTask.Start ();
控制台。WriteLine(“父任务完成。”);
});
parentTask.Wait ();
控制台。WriteLine(“主线程完成。”);
当我们调用parentTask.Wait()时,即使我们指定了TaskCreationOptions,也不会等待childTask。attachedtopparent,这是因为TaskCreationOptions。DenyChildAttach禁止儿童附加到它。如果你用Task. factory . startnew而不是Task. factory . startnew运行相同的代码。运行时,parentTask.Wait()将等待childTask,因为Task.Factory.StartNew使用TaskCreationOptions。没有一个
TaskScheduler - Task.Run uses TaskScheduler.Default which means that the default task scheduler (the one that runs tasks on Thread Pool) will always be used to run tasks. Task.Factory.StartNew on the other hand uses TaskScheduler.Current which means scheduler of the current thread, it might be TaskScheduler.Default but not always. In fact when developing Winforms or WPF applications it is required to update UI from the current thread, to do this people use TaskScheduler.FromCurrentSynchronizationContext() task scheduler, if you unintentionally create another long running task inside task that used TaskScheduler.FromCurrentSynchronizationContext() scheduler the UI will be frozen. A more detailed explanation of this can be found here
所以一般来说,如果你不使用嵌套的子任务,并且总是希望你的任务在线程池上执行,最好使用任务。运行,除非您有一些更复杂的场景。
第二种方法,Task。在. net框架的最新版本(. net 4.5)中引入了Run。
但是,第一个方法Task.Factory。StartNew,让你有机会定义关于你想要创建的线程的很多有用的东西,而Task。Run没有提供这个。
例如,假设您想创建一个长时间运行的任务线程。如果线程池中的一个线程将用于此任务,则可以认为这是对线程池的滥用。
为了避免这种情况,您可以在单独的线程中运行任务。一个新创建的线程,它将专门用于此任务,并且在任务完成后将被销毁。你不能通过任务来实现这一点。运行,而您可以使用Task.Factory这样做。StartNew,如下所示:
Task.Factory.StartNew(..., TaskCreationOptions.LongRunning);
如这里所述:
因此,在.NET Framework 4.5开发人员预览中,我们已经介绍了
新任务。运行方法。这不会淘汰Task.Factory.StartNew,
而应该简单地认为这是一种快速使用的方法
task。factory。startnew而不需要指定一堆
参数。这是一条近路。事实上,任务。Run实际上是
以Task.Factory.StartNew相同的逻辑实现,
只是传入一些默认参数。当您将一个Action传递给
的任务。运行:
Task.Run(someAction);
这完全等价于:
Task.Factory.StartNew(someAction,
CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);