在当前线程上执行任务

时间:2013-11-19 08:08:35

标签: c# task-parallel-library

是否可以强制任务在当前线程上同步执行?

也就是说,是否可能通过例如将一些参数传递给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()等操作。

5 个答案:

答案 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());
}