1

What's the difference between these 2 snippets of code? If we have the following Anonymous function:

Func<object, Task<bool> foo = async (str) => { /*misc async code with awaits*/ return result};

What's the difference between calling it directly:

Task<bool> bar = foo("myparam");

and using a task:

Task<bool> bar = Task.Factory.StartNew(foo, "myparam").Unwrap();

In essence, they both execute asynchronously, but what are the pro's/con's of doing it one way over the other? they both return Tasks. how are the returned tasks different? (e.g. is one multi-threaded, while the other single threaded, etc.)

WillFM
  • 353
  • 1
  • 13
  • `foo("myparam")` invoke `foo` in current thread. `StartNew` schedule `foo` with current task scheduler (or whatever task scheduler configured for `Factory`). – user4003407 Apr 13 '16 at 19:52
  • Essentially the same question asked and answered in a different way: http://stackoverflow.com/questions/34680985/what-is-the-difference-between-asynchronous-programming-and-multithreading – Kilanash Apr 13 '16 at 19:56
  • @Kilanash, after reading that. your saying, that direct invoke, will start a same thread async task, while the Factory, could 'possibly' spawn a separate thread? From what I understand on my reading on Task (and the default Factory) there's no guarantee that a new thread would be created. – WillFM Apr 13 '16 at 20:29
  • I was looking at the msdn page for [System.Threading.Tasks](https://msdn.microsoft.com/en-us/library/system.threading.tasks.task(v=vs.110).aspx) - the section 'Waiting for tasks to complete' states: "Because tasks typically run asynchronously on a thread pool thread..." whereas Eric Lippert's answer, and the conversation in the comments beneath it, imply async doesn't necessarily always spawn a new thread, instead he likens it to syntactic sugar over `yield return`. – Kilanash Apr 13 '16 at 20:31
  • And this is where my confusion falls, they both return Tasks, so what's the difference between these 2 tasks? I've updated my question to clarify on this. – WillFM Apr 13 '16 at 20:43

3 Answers3

2

In essence, they both execute asynchronously, but what are the pro's/con's of doing it one way over the other?

Calling the method directly will execute it asynchronously on the current thread. The called method will inherit the context of the calling method, and will use the current SynchronizationContext (or, if that is null, the current TaskScheduler) to resume executing. I explain this in full on my blog.

Calling the method via StartNew will execute it asynchronously on the current TaskScheduler. Generally, this is the thread pool task scheduler, unless the calling code is executing as part of a Delegate Task (a term defined on my blog). Unless that task was started with the HideScheduler option (described on my blog), in which case there is no current TaskScheduler even though there is a TaskScheduler executing that code.

If the StartNew scenario sounds complicated, that's because it is. StartNew is only for experts. I have an entire blog post on why StartNew should not be used.

A more realistic comparison would be between calling the method directly and calling the method via Task.Run. Unlike StartNew, Task.Run always executes its code on a thread pool thread, so the method will run asynchronously on the thread pool.

For real-world code, you should only use Task.Run when you need to. If the method is properly asynchronous (i.e., it doesn't calculate a fractal first or anything), then you shouldn't use Task.Run. And you shouldn't use StartNew at all.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Thanks Stephen, this is pretty much the clarification I was looking for. Most examples I find on async Anonymous actions/funcs. have people using Task.Factory.StartNew or Task.Run. It seamed superfluous to me when writing async code, and that's why I was looking for clarification. – WillFM Apr 14 '16 at 18:34
1

The first call 'Task bar = foo("myparam")` returns a task that represents the actual execution of the Func

The second call Task.Factory.StartNew returns a Task<Task<bool>> representing an asynchronous operation that returns another Task<bool> that now represents the execution of the lambda

The call to UnWrap() returns a new proxy tasks that represents the completion of the inner task.

korir
  • 51
  • 4
1

I made a quick example of the above at https://dotnetfiddle.net/v1kFjw, which creates the func, assigns it some potentially long-running work, adds some debug print statements as a timeline reference around it, then invokes it in both ways.

Using the View IL function, the following seems to be the difference, as korir stated:

Directly invoking the func creates the async/await state machine pattern around the code directly inside the func, then begins running the code inside the function until it hits await, upon which the state machinery kicks in, 'returns' to the original caller (when I say returns, it's not a literal return, it's that the code following the invocation is silently wrapped up into the state machine), and continues, interleaved with calls to the awaited code, polling it for completion.

Using Task.Factory.StartNew creates a new thread that runs regardless of what code is following it - this includes the program perhaps exiting before the thread you spun up even completes.

In the example, a continuation task to show completion had to be added, as well as a Task.WaitAll at the end to guarantee it finished.

I was just doing the research for interest's sake - if anyone has more insight or can point out flaws please do.

Edit: couple of things to note after I posted this: dotnetfiddle has execution time and output size restrictions which affects the results of the test - in the end I used Linqpad to do testing as that was more reliable. Also noted that calling the async function but not using the return value seemed to, again, run into the issue of not guaranteeing the full runtime of the function due to the main thread exiting before the result could finish. In answer to your comment about .Unwrap() not being used - ContinueWith() doesn't seem to be compatible with it, at least according to the compiler.

In the end I defer to Stephen Cleary's answer as he is clearly more knowledgeable and qualified. :)

References:

Kilanash
  • 4,479
  • 1
  • 14
  • 11
  • Whats the difference between: `Task bar = Task.Factory.StartNew(foo, "myparam") // factory passes state` and `Task bar = Task.Factory.StartNew(() => foo("myparam")) // implicitly passing state` – WillFM Apr 13 '16 at 23:30
  • Also, you haven't unwrapped the Factory Task like I show in my example in the question. interestingly enough though, `ContinueWith` runs in a new thread, when spawned from the task returned from the direct invoke. while using the factory appears to use the same thread. – WillFM Apr 13 '16 at 23:34
  • Difference between passing state through StartNew and using a lambda to implicitly pass state is simply static type awareness. "myparam" in the first case is passed using object, and you will have to cast the state in your task function (potentially causing type conversion errors at runtime) whereas using a lambda gets you compile-time type checks. – Kilanash Apr 14 '16 at 14:55