TaskEx.Yield(TaskScheduler)

半腔热情 提交于 2019-12-23 17:20:10

问题


Last month I asked the following question which resulted in my learning of TaskEx.Yield:

Can async methods have expensive code before the first 'await'?

However, I have since realized that this method actually submits all subsequent code to the ambient TaskScheduler. In true DI spirit, our team has agreed to avoid using ambient instances where possible, so I would like to know if it's possible to explicitly specify a TaskScheduler to use?

Something like the following would be great:

public static YieldAwaitable Yield(TaskScheduler taskScheduler)
{
    return new YieldAwaitable(taskScheduler);
}

However, the current implementation of Async CTP only offers:

public static YieldAwaitable Yield()
{
    return new YieldAwaitable(SynchronizationContext.Current ?? TaskScheduler.Current);
}

Would the following provide an acceptably efficient alternative?

await Task.Factory.StartNew(() => { }, CancellationToken.None, TaskCreationOptions.None, this.TaskScheduler);

回答1:


In true DI spirit, our team has agreed to avoid using ambient instances where possible...

The async language support is based around an implicit scheduling context. I don't see the need for dependency injection here. Any method calling your async method may supply its own context if necessary.

Your alternative:

await Task.Factory.StartNew(() => { }, CancellationToken.None, TaskCreationOptions.None, this.TaskScheduler);

will not work as expected. This will queue a noop lambda to a specific TaskScheduler and then resume the method on the implicit scheduling context.

An earlier version of the Async CTP did provide a "yield to another context" method, called SwitchTo. It was removed because it's too easy to misuse.

Personally, I think it's cleaner to keep your async code using its implicit scheduling context, which is provided by its calling method.

P.S. It's not (too) difficult to create and install your own context, e.g., for testing purposes. I wrote AsyncContext as a simple scheduling context for unit testing and Console programs. The Async CTP comes with GeneralThreadAffineContext, WindowsFormsContext, and WpfContext for testing. Any of these can be installed using SynchronizationContext.SetSynchronizationContext. IMO, DI is overkill.




回答2:


The language support for async allows for awaitables to control their own scheduling. There is default scheduling that kicks in when you await a Task, but there are many ways you can change the default behavior with a little bit more code.

Specifically, await is intended to do the following:

  1. Test to see if the awaitable is "done" (GetAwaiter().IsCompleted).
  2. If (and only if) the awaitable wasn't done, ask for it to schedule the rest of the method (GetAwaiter().OnCompleted(...))
  3. "Realize" the awaitable's result. This means either give back the returned value or ensure that the exceptions encountered reemerge.

So keep in mind that if the awaitable claimed it was "done", then nothing is ever scheduled.

Task.Yield() is novel because it gives back an awaitable that is never done, for the express purpose of giving you a way to explicitly stop execution for now, and immediately schedule the rest up for execution. It uses the ambient context, but there are many other ways to do similarly without ambient contexts.

An example of overriding the default behavior is when you await an incomplete task, but using the ConfigureAwait(false) method. ConfigureAwait(false) wraps the task in a special awaitable that always uses the default task scheduler, effectively always resuming on the thread pool. 'false' is there to explicitly ignore the ambient sync context.

There isn't a Task.Yield().ConfigureAwait(false), but consider the following hypothetical:

// ... A ...
await Task.Yield().ConfigureAwait(false);
// ... B ...

The above can be pretty much achieved by

// ... A ...
await Task.Run(() => {
    // ... B ...
});

There is a little more explicitness and nesting there, but this isn't necessarily bad considering what's happening. Part 'A' always runs on the invoking thread, whereas part 'B' always runs on the thread pool. There are definitely differences on how you should look at code that is in sections A and B, so thus having some more curlies in between should hopefully get people to pause before assuming both sections are the same context.



来源:https://stackoverflow.com/questions/8742522/taskex-yieldtaskscheduler

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!