【问题标题】: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 “我知道你从哪里来”——那么我从哪里来?

标签: c# task-parallel-library


【解决方案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

【讨论】:

【解决方案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() 调用最终在不同的线程上。可能是我做错了什么,但我厌倦了尝试......
    猜你喜欢
    • 2013-04-08
    • 2014-01-19
    • 1970-01-01
    • 1970-01-01
    • 2011-08-05
    • 1970-01-01
    • 2016-03-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多