Execute task on current thread

后端 未结 5 1021
灰色年华
灰色年华 2021-02-05 05:35

Is it possible to force a task to execute synchronously, on the current thread?

That is, is it possible, by e.g. passing some parameter to StartNew(), to ma

相关标签:
5条回答
  • 2021-02-05 06:01

    Yes, you can pretty much do that using custom task schedulers.

    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());
    }
    
    0 讨论(0)
  • 2021-02-05 06:03

    Since you mention testing, you might prefer using a TaskCompletionSource<T> since it also lets you set an exception or set the task as cancelled (works in .Net 4 and 4.5):

    Return a completed task with a result:

    var tcs = new TaskCompletionSource<TRet>();
    tcs.SetResult(func());
    return tcs.Task;
    

    Return a faulted task:

    var tcs = new TaskCompletionSource<TRet>();
    tcs.SetException(new InvalidOperationException());
    return tcs.Task;
    

    Return a canceled task:

    var tcs = new TaskCompletionSource<TRet>();
    tcs.SetCanceled();
    return tcs.Task;
    
    0 讨论(0)
  • 2021-02-05 06:15

    You can simply return the result of func() wrapped in a Task.

    public class NoThreading : IThreads
    {
        public Task<TRet> StartNew<TRet>(Func<TRet> func)
        {
            return Task.FromResult(func());
        }
    }
    

    Now you can attach "continue with" tasks to this.

    0 讨论(0)
  • 2021-02-05 06:24

    OP here. This is my final solution (which actually solves a lot more than I asked about).

    I use the same implementation for Threads in both test and production, but pass in different 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);
        }
    }
    

    I wrap the Task in a TaskContinuation class in order to be able to specify TaskScheduler for the ContinueWith() call.

    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);
        }
    }
    

    I create my custom TaskScheduler that dispatches the action on the thread that scheduler was created on:

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

    Now I can specify the behaviour by passing in different TaskSchedulers to the Threads constructor.

    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
    

    Finally, since the event loop doesn't run automatically in my unit test, I have to execute it manually. Whenever I need to wait for a background operation to complete I execute the following (from the main thread):

    DispatcherHelper.DoEvents();
    

    The DispatcherHelper can be found here.

    0 讨论(0)
  • 2021-02-05 06:27

    Task scheduler decides whether to run a task on a new thread or on the current thread. There is an option to force running it on a new thread, but none forcing it to run on the current thread.

    But there is a method Task.RunSynchronously() which

    Runs the Task synchronously on the current TaskScheduler.

    More on MSDN.

    Also if you are using async/await there is already a similar question on that.

    0 讨论(0)
提交回复
热议问题