在当前线程上执行任务

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&lt;TRet&gt; 的实例,我可以在该实例上执行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&lt;T&gt;,因为它还允许您设置异常或将任务设置为已取消(适用于 .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() 调用最终在不同的线程上。可能是我做错了什么,但我厌倦了尝试......

以上是关于在当前线程上执行任务的主要内容,如果未能解决你的问题,请参考以下文章

并发编程——协程

java线程池与tomcat线程池策略算法上的区别

GCD的小结

线程池的执行流程

多线程, Thread类,Runnable接口

1.1多线程上下文切换