是否可以强制任务在当前线程上同步执行?
也就是说,是否可能通过例如将一些参数传递给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()
等操作。
答案 0 :(得分:11)
您只需返回func()
中包含的Task
的结果。
public class NoThreading : IThreads
{
public Task<TRet> StartNew<TRet>(Func<TRet> func)
{
return Task.FromResult(func());
}
}
现在您可以将“继续”任务附加到此。
答案 1 :(得分:6)
由于您提到测试,您可能更喜欢使用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;
答案 2 :(得分:5)
任务计划程序决定是在新线程上还是在当前线程上运行任务。有一个选项强制在新线程上运行它,但没有强制它在当前线程上运行。
但是有一种方法Task.RunSynchronously()
在当前的TaskScheduler上同步运行任务。
更多关于MSDN。
此外,如果您使用的是async/await
,则表明已有similar question。
答案 3 :(得分:4)
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<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
课程中,以便能够为TaskScheduler
电话指定ContinueWith()
。
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。
答案 4 :(得分:3)
是的,你可以使用自定义任务调度程序来做到这一点。
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());
}