Why Was "Switchto" Removed from Async Ctp/Release

Why was SwitchTo removed from Async CTP / Release?

Stephen Toub has some more information on the reasoning in this thread.

To summarize, it's not a good idea for two reasons:

  1. It promotes unstructured code. If you have "heavy processing" that you need to do, it should be placed in a Task.Run. Even better, separate your business logic from your UI logic.
  2. Error handling and (some) continuations run in an unknown context. catch/finally blocks in Test would need to handle running in a thread pool or UI context (and if they're running in the thread pool context, they can't use SwitchTo to jump on the UI context). Also, as long as you await the returned Task you should be OK (await will correct the continuation context if necessary), but if you have explicit ContinueWith continuations that use ExecuteSynchronously, then they'll have the same problem as the catch/finally blocks.

In short, the code is cleaner and more predictable without SwitchTo.

When is it asynchronous and when is synchronous

This answer contains general info and advices, and it's not focused on the code that you posted.

  1. Being confused while learning async-await is OK. Getting a perfect understanding of async-await without going through a confusion phase is practically almost impossible IMHO.
  2. The await Task.Yield(), await Task.Delay(1), await Task.Run(() => { }), and .ConfigureAwait(false) are dirty hacks that people sometimes use out of frustration that an explicit way to switch imperatively to the ThreadPool context does not exist (in the standard libraries). A SwitchTo method existed in some pre-prelease .NET version, then it was removed for technical reasons, then the technical reasons were eliminated, reintroducing the method never became a high enough priority, and so it didn't happen. It may happen in the future, but don't hold your breath. It is currently available in the Microsoft.VisualStudio.Threading package, if you want it.
  3. You don't need any of these hacks to write async-await code successfully. If you want to offload work to the ThreadPool, there is the Task.Run method that does the job perfectly.
  4. Offloading work to the ThreadPool should be done at the "application code" level, not at the "library code" level. You can read this article to understand why exposing asynchronous wrappers for synchronous methods is not a good idea.

What exactly in an awaitable is being awaited? How do I make a Task properly?

so what exactly is being awaited?

Nothing is being awaited. Await means asynchronous wait. For a wait to be asynchronous, the awaitable (the Task) should not be completed at the await point. In your case the awaitable is already completed (the IsCompleted property of the TaskAwaiter returns true), so the async state machine grabs immediately its result and proceeds with the next line as usual. There is no reason to pack the current state of the machine, invoke the OnCompleted method of the awaiter, and hand back an incomplete Task to the caller.

If you want to offload specific parts of an asynchronous method to the ThreadPool, the recommended way is to wrap these parts in Task.Run. Example:

public async Task<int> LongOperationWithAnInt32ResultAsync(string input)
{
/// Section A
_ = int.TryParse(input, out var parsedInt)
parsedInt = await Task.Run(() => SyncOperationWithAnInt32Result(parsedInt));

/// Section B
await Task.Run(async () => await MyCustomTaskThatIWantAwaited());

/// Section C
return parsedInt;
}

If you like the idea of controlling imperatively the thread where the code is running, there is a SwitchTo extension method available in the Microsoft.VisualStudio.Threading package. Usage example:

await TaskScheduler.Default.SwitchTo(); // Switch to the ThreadPool

The opinion of the experts is to avoid this approach, and stick with the Task.Run.

Better solution for sync/async problem in desktop app?

If I want to solve blocking problem, I could call the library in Task.Run like this:

It solves the problem, UI is not blocke anymore, but I've consumed one thread from ThreadPool. It is not good solution.

This is exactly what you want to do in a WinForms app. CPU-intensive code should be moved to a separate thread to free up the UI thread. There isn't any downside to consuming a new thread in WinForms.

Use Task.Run to move it to a different thread, and wait asynchronously from the UI thread for it to complete.

To quote the Asynchronous programming article from Microsoft:

If the work you have is CPU-bound and you care about responsiveness, use async and await, but spawn off the work on another thread with Task.Run.

Where can i find the ThreadPool.SwitchTo method?

For reference, CharlesO answered his question within the comments above:

OK, found it guys, Summary: Provides
methods for interacting with the
ThreadPool. Remarks: ThreadPoolEx is a
placeholder.

Public Shared Function SwitchTo() As
System.Runtime.CompilerServices.YieldAwaitable
Member of
System.Threading.ThreadPoolEx Summary:
Creates an awaitable that
asynchronously yields to the
ThreadPool when awaited.

The lack of non-capturing Task.Yield forces me to use Task.Run, why follow that?

You don't have to put the Task.Run in DoWorkAsync. Consider this option:

public async Task UIAction()
{
// UI Thread
Log("UIAction");

// start the CPU-bound work
var cts = new CancellationTokenSource(5000);
var workTask = Task.Run(() => DoWorkAsync(cts.Token));

// possibly await for some IO-bound work
await Task.Delay(1000);
Log("after Task.Delay");

// finally, get the result of the CPU-bound work
int c = await workTask;
Log("Result: {0}", c);
}

This results in code with much clearer intent. DoWorkAsync is a naturally synchronous method, so it has a synchronous signature. DoWorkAsync neither knows nor cares about the UI. The UIAction, which does care about the UI thread, pushes off the work onto a background thread using Task.Run.

As a general rule, try to "push" any Task.Run calls up out of your library methods as much as possible.

Continuation on multiple concurrent threads in async method after async IO API call

Will CpuNoIOWork() run concurrently on multiple threads (using the thread pool) as the IO calls complete or will it only use 1 thread at a time?

It will run concurrently, on thread pool threads.

However, depending on the internals of what you're awaiting, it might continue on an I/O thread pool thread, which is not desirable. If you have CPU-bound work, I recommend wrapping that in Task.Run, which will ensure the work will be run on a worker thread pool thread:

var result = await File.ReadAllTextAsync($"File{fileNum}").ConfigureAwait(false);
await Task.Run(() => CpuNoIOWork(result));

await task.ConfigureAwait(false) versus await ContextSwitcher.SwitchToThreadPool()

As others have noted, ConfigureAwait(false) is less necessary with modern code (in particular, since ASP.NET Core has gone mainstream). Whether to use it in your library at this point is a judgement call; personally, I still do use it, but my main async library is very low-level.

especially given the fact the code after await Do1Async().ConfigureAwait(false) will continue on exactly the same conditions as the code after await ContextSwitcher.SwitchToThreadPool() ?

The conditions aren't exactly the same - there's a difference if Do1Async completes synchronously.

Why is the 1st option considered a good practice and this one isn't

As explained by Stephen Toub, the "switcher" approach does allow code like this:

try
{
await Do1Async(); // UI thread
await ContextSwitcher.SwitchToThreadPool();
await Do2Async(); // Thread pool thread
}
catch (Exception)
{
... // Unknown thread
}

Specifically, catch and finally blocks can run in an unknown thread context, depending on runtime behavior. Code that can run on multiple threads is harder to maintain. This is the main reason it was cut from the Async CTP (also bearing in mind that with the language at that time, you couldn't await in a catch or finally, so you couldn't switch to the desired context).

IIRC, this is still better than the ContextSwitcher option, but why?

I think it's important to note that these are all different semantics - in particular, they are all saying quite different things:

  • await x.ConfigureAwait(false) says "I don't care what thread I resume on. It can be the same thread or a thread pool thread, whatever." Note: it does not say "switch to a thread pool thread."
  • await ContextSwitcher() says "Switch to this context and continue executing".
  • await Task.Run(...) says "Run this code on a thread pool thread and then resume me."

Out of all of those, I prefer the first and third. I use ConfigureAwait(false) to say "this method doesn't care about the thread it resumes on", and I use Task.Run to say "run this code on a background thread". I don't like the "switcher" approach because I find it makes the code less maintainable.

Is it safe to just remove the synchronization context like that, which AFAIU would affect the whole synchronous scope after await new SynchronizationContextRemover() ?

Yes; the tricky part is that it needs to affect the synchronous scope. This is why there is no Func<Task> or Func<Task<T>> overloads for my SynchronizationContextSwitcher.NoContext method, which does pretty much the same thing. The one major difference between NoContext and SynchronizationContextRemover is that mine forces a scope (a lambda) in which there is no context and the remover is in the form of a "switcher". So again, mine forces the code to say "run this code without a context", whereas the switcher says "at this point in my method, remove the context, and then continue executing". In my opinion, the explicitly-scoped code is more maintainable (again, considering catch/finally blocks), which is why I use that style of API.

How is this SynchronizationContextRemover better than ContextSwitcher, besides perhaps it has one less switch to a pool thread?

SynchronizationContextRemover and NoContext both stay on the same thread; they just temporarily remove the context on that thread. ContextSwitcher does actually switch to a thread pool thread.

Push async method's execution to thread pool thread

Your await Task.Delay(0).ConfigureAwait(false); is a poor mans Task.Yield() attempt (which does not work because I guess that Delay optimizes itself out if the argument is zero).

I would not recommend yield here.

I think in your click handler you should push to the thread-pool:

await Task.Run(async () => await Foo());

This is very simple and always works if the method you are calling does not depend on the synchronization context of the UI. This is architecturally nice because non of the methods you are calling need or should be aware by what code they are called.



Related Topics



Leave a reply



Submit