在当前线程上执行任务
是否可以在当前线程上强制执行任务同步执行?
也就是说,通过例如将一些参数传递给StartNew()
,可以生成此代码:
Task.Factory.StartNew(() => ThisShouldBeExecutedSynchronously());
表现得像这样:
ThisShouldBeExecutedSynchronously();
背景:
我有一个名为IThreads
的界面:
public interface IThreads { Task StartNew(Func func); }
我想有两个这样的实现,一个使用线程的普通:
public class Threads : IThreads { public Task StartNew(Func func) { return Task.Factory.StartNew(func); } }
而且一个不使用线程(在某些测试场景中使用):
public class NoThreading : IThreads { public Task StartNew(Func func) { // What do I write here? } }
我可以让NoThreading
版本只调用func()
,但我想返回一个Task
的实例,我可以在其上执行ContinueWith()
等操作。
任务计划程序决定是在新线程上还是在当前线程上运行任务。 有一个选项强制在新线程上运行它,但没有强制它在当前线程上运行。
但是有一个方法Task.RunSynchronously()
在当前TaskScheduler上同步运行任务。
更多关于MSDN 。
此外,如果您使用async/await
,则已经存在类似的问题 。
您只需返回包装在Task
的func()
的结果即可。
public class NoThreading : IThreads { public Task StartNew (Func func) { return Task.FromResult(func()); } }
现在您可以将“继续”任务附加到此。
由于您提到了测试,您可能更喜欢使用TaskCompletionSource
因为它还允许您设置exception或将任务设置为已取消(在.Net 4和4.5中工作):
返回已完成的任务,结果如下:
var tcs = new TaskCompletionSource(); tcs.SetResult(func()); return tcs.Task;
返回故障任务:
var tcs = new TaskCompletionSource(); tcs.SetException(new InvalidOperationException()); return tcs.Task;
返回已取消的任务:
var tcs = new TaskCompletionSource(); tcs.SetCanceled(); return tcs.Task;
OP在这里。 这是我的最终解决方案(实际上解决的问题比我提出的要多得多)。
我在测试和生产中使用相同的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 StartNew (Func func) { var task = Task.Factory.StartNew(func, CancellationToken.None, TaskCreationOptions.None, _executeScheduler); return new TaskContinuation (task, _continueScheduler); } }
我将Task
包装在TaskContinuation
类中,以便能够为ContinueWith()
调用指定TaskScheduler
。
public class TaskContinuation { private readonly Task _task; private readonly TaskScheduler _scheduler; public TaskContinuation(Task task, TaskScheduler scheduler) { _task = task; _scheduler = scheduler; } public void ContinueWith(Action> 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(() => TryExecuteTask(task))); } protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) { return true; } protected override IEnumerable GetScheduledTasks() { return Enumerable.Empty (); } }
现在我可以通过将不同的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
最后,由于事件循环不会在我的unit testing中自动运行,我必须手动执行它。 每当我需要等待后台操作完成时,我执行以下操作(从主线程):
DispatcherHelper.DoEvents();
DispatcherHelper
可以在这里找到。
是的,你可以使用自定义任务调度程序来做到这一点。
internal class MyScheduler : TaskScheduler { protected override IEnumerable GetScheduledTasks() { return Enumerable.Empty (); } 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()); }