在当前线程上执行任务
Posted
技术标签:
【中文标题】在当前线程上执行任务【英文标题】:Execute task on current thread 【发布时间】:2013-11-19 08:08:34 【问题描述】:是否可以强制任务在当前线程上同步执行?
也就是说,是否有可能,例如将一些参数传递给StartNew()
,以制作此代码:
Task.Factory.StartNew(() => ThisShouldBeExecutedSynchronously());
表现得像这样:
ThisShouldBeExecutedSynchronously();
背景:
我有一个叫IThreads
的接口:
public interface IThreads
Task<TRet> StartNew<TRet>(Func<TRet> func);
我想对此有两种实现方式,一种使用线程的普通方法:
public class Threads : IThreads
public Task<TRet> StartNew<TRet>(Func<TRet> func)
return Task.Factory.StartNew(func);
还有一个不使用线程的(在某些测试场景中使用):
public class NoThreading : IThreads
public Task<TRet> StartNew<TRet>(Func<TRet> func)
// What do I write here?
我可以让NoThreading
版本只调用func()
,但我想返回一个Task<TRet>
的实例,我可以在该实例上执行ContinueWith()
等操作。
【问题讨论】:
NoThreading 实现使用了哪些测试场景?存在一个实际上与线程无关的 IThreads 实现似乎很奇怪。 @Todd Bowles:我非常不喜欢人们回答问题而不是回答问题时说“嗯,你为什么要问这个?你不应该问这个。” @SimpleFellow 我了解您来自哪里,但提供帮助的一个重要部分是了解问题和背景。在这种情况下,IThreads 感觉就像一个泄漏的抽象,它的非线程实现会让未来的开发人员感到困惑。 @ToddBowles 如果我将接口名称IThreads
更改为例如ITasks
?相比之下,Task.Factory.StartNew()
不一定会启动一个新线程。所以有了更好的名字,抽象泄漏就会消失,问题是有效的,对吧?
@Todd Bowles “我知道你从哪里来”——那么我从哪里来?
【参考方案1】:
您可以简单地返回包裹在Task
中的func()
的结果。
public class NoThreading : IThreads
public Task<TRet> StartNew<TRet>(Func<TRet> func)
return Task.FromResult(func());
现在您可以在此附加“继续”任务。
【讨论】:
不错的解决方案。对我来说不幸的是它需要 .NET 4.5。 @TorbjörnKalin 你可以在 .Net 4.0 上做同样的事情,只是更冗长。看看TaskCompletionSource
(也在艾伦的回答中解释过)。【参考方案2】:
任务调度程序决定是在新线程上还是在当前线程上运行任务。有一个选项可以强制在新线程上运行它,但没有强制它在当前线程上运行。
但是有一个方法Task.RunSynchronously()
在当前 TaskScheduler 上同步运行任务。
更多关于MSDN。
另外,如果您使用的是async/await
,那么上面已经有一个similar question。
【讨论】:
不幸的是,RunSynchronously
并不总是同步运行任务。它有similar corner cases as ExecuteSynchronously
。【参考方案3】:
由于您提到了测试,您可能更喜欢使用 TaskCompletionSource<T>
,因为它还允许您设置异常或将任务设置为已取消(适用于 .Net 4 和 4.5):
返回一个已完成的任务和结果:
var tcs = new TaskCompletionSource<TRet>();
tcs.SetResult(func());
return tcs.Task;
返回一个错误的任务:
var tcs = new TaskCompletionSource<TRet>();
tcs.SetException(new InvalidOperationException());
return tcs.Task;
返回一个取消的任务:
var tcs = new TaskCompletionSource<TRet>();
tcs.SetCanceled();
return tcs.Task;
【讨论】:
TaskCompletionSource 非常棒..很有用..有时。【参考方案4】:在这里。这是我的最终解决方案(实际上解决的问题比我问的要多得多)。
我在测试和生产中对Threads
使用相同的实现,但传入不同的TaskSchedulers
:
public class Threads
private readonly TaskScheduler _executeScheduler;
private readonly TaskScheduler _continueScheduler;
public Threads(TaskScheduler executeScheduler, TaskScheduler continueScheduler)
_executeScheduler = executeScheduler;
_continueScheduler = continueScheduler;
public TaskContinuation<TRet> StartNew<TRet>(Func<TRet> func)
var task = Task.Factory.StartNew(func, CancellationToken.None, TaskCreationOptions.None, _executeScheduler);
return new TaskContinuation<TRet>(task, _continueScheduler);
我将Task
包装在TaskContinuation
类中,以便能够为ContinueWith()
调用指定TaskScheduler
。
public class TaskContinuation<TRet>
private readonly Task<TRet> _task;
private readonly TaskScheduler _scheduler;
public TaskContinuation(Task<TRet> task, TaskScheduler scheduler)
_task = task;
_scheduler = scheduler;
public void ContinueWith(Action<Task<TRet>> func)
_task.ContinueWith(func, _scheduler);
我创建了我的自定义 TaskScheduler
,它在创建调度程序的线程上调度操作:
public class CurrentThreadScheduler : TaskScheduler
private readonly Dispatcher _dispatcher;
public CurrentThreadScheduler()
_dispatcher = Dispatcher.CurrentDispatcher;
protected override void QueueTask(Task task)
_dispatcher.BeginInvoke(new Func<bool>(() => TryExecuteTask(task)));
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
return true;
protected override IEnumerable<Task> GetScheduledTasks()
return Enumerable.Empty<Task>();
现在我可以通过将不同的TaskSchedulers
传递给Threads
构造函数来指定行为。
new Threads(TaskScheduler.Default, TaskScheduler.FromCurrentSynchronizationContext()); // Production
new Threads(TaskScheduler.Default, new CurrentThreadScheduler()); // Let the tests use background threads
new Threads(new CurrentThreadScheduler(), new CurrentThreadScheduler()); // No threads, all synchronous
最后,由于事件循环不会在我的单元测试中自动运行,我必须手动执行它。每当我需要等待后台操作完成时,我都会执行以下操作(从主线程):
DispatcherHelper.DoEvents();
DispatcherHelper
可以找到here。
【讨论】:
【参考方案5】:是的,您几乎可以使用自定义任务计划程序来做到这一点。
internal class MyScheduler : TaskScheduler
protected override IEnumerable<Task> GetScheduledTasks()
return Enumerable.Empty<Task>();
protected override void QueueTask(Task task)
base.TryExecuteTask(task);
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
base.TryExecuteTask(task);
return true;
static void Main(string[] args)
Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " Main");
Task.Factory.StartNew(() => ThisShouldBeExecutedSynchronously(), CancellationToken.None, TaskCreationOptions.None, new MyScheduler());
【讨论】:
这实际上最终成为了我的解决方案,创建了我自己的 TaskScheduler。使用其他解决方案,我最终遇到了其他问题,例如无法在主线程上运行ContinueWith()
。这个一直有效。
@TorbjörnKalin Oh.. 你为什么不能在主线程上使用ContinueWith
?
我在使用TaskScheduler.FromCurrentSynchronizationContext()
时遇到错误。找到了该here 的解决方案,但是在使用它时,ContinueWith()
调用最终在不同的线程上。可能是我做错了什么,但我厌倦了尝试......以上是关于在当前线程上执行任务的主要内容,如果未能解决你的问题,请参考以下文章