221

Every blog post I've read tells you how to consume an asynchronous method in C#, but for some odd reason never explain how to build your own asynchronous methods to consume. So I have this code right now that consumes my method:

private async void button1_Click(object sender, EventArgs e)
{
    var now = await CountToAsync(1000);
    label1.Text = now.ToString();
}

And I wrote this method that is CountToAsync:

private Task<DateTime> CountToAsync(int num = 1000)
{
    return Task.Factory.StartNew(() =>
    {
        for (int i = 0; i < num; i++)
        {
            Console.WriteLine("#{0}", i);
        }
    }).ContinueWith(x => DateTime.Now);
}

Is this, the use of Task.Factory, the best way to write an asynchronous method, or should I write this another way?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Khalid Abuhakmeh
  • 10,709
  • 10
  • 52
  • 75
  • 26
    I'm asking a general question about how to structure a method. I just want to know where to start in turning my already synchronous methods into asynchronous ones. – Khalid Abuhakmeh Apr 17 '13 at 15:30
  • 2
    OK, so what does a typical synchronous method *do*, and *why do you want to make it asynchronous*? – Eric Lippert Apr 17 '13 at 15:32
  • Let's say I have to batch process a bunch of files and return a result object. – Khalid Abuhakmeh Apr 17 '13 at 15:36
  • 1
    OK, so: (1) what is the high-latency operation: obtaining the files -- because the network could be slow, or whatever -- or doing the processing -- because it is CPU intensive, say. And (2) you still haven't said why you want it to be asynchronous in the first place. Is there a UI thread that you want to not block, or what? – Eric Lippert Apr 17 '13 at 15:56
  • 28
    @EricLippert The example give by the op is very basic, it really doesn't need to be that complicated. – David B. Dec 15 '15 at 20:05
  • Agreed with David. @EricLipper, I really feel this is perfect question with reasonable details. – Harshan Oct 16 '21 at 17:56

3 Answers3

262

I don't recommend StartNew unless you need that level of complexity.

If your async method is dependent on other async methods, the easiest approach is to use the async keyword:

private static async Task<DateTime> CountToAsync(int num = 10)
{
  for (int i = 0; i < num; i++)
  {
    await Task.Delay(TimeSpan.FromSeconds(1));
  }

  return DateTime.Now;
}

If your async method is doing CPU work, you should use Task.Run:

private static async Task<DateTime> CountToAsync(int num = 10)
{
  await Task.Run(() => ...);
  return DateTime.Now;
}

You may find my async/await intro helpful.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 18
    @Stephen: "If your async method is dependanton other async methods" - ok, but what if that is not the case. What if were trying to wrap some code that calls BeginInvoke with some callback code? – Ricibob Aug 27 '13 at 10:46
  • 2
    @Ricibob: You should use `TaskFactory.FromAsync` to wrap `BeginInvoke`. Not sure what you mean about "callback code"; feel free to post your own question with code. – Stephen Cleary Aug 27 '13 at 12:05
  • 1
    Stephen, in the for loop, is the next iteration called immediately or after the `Task.Delay` returns? –  Jan 13 '15 at 17:39
  • 3
    @jp2code: `await` is an "asynchronous wait", so it does not proceed to the next iteration until after the task returned by `Task.Delay` completes. – Stephen Cleary Jan 13 '15 at 19:56
  • @StephenCleary Nice answer, and nice blog too. +10 :) – Ian Jan 08 '16 at 14:01
  • Another great [MSDN article](https://msdn.microsoft.com/en-us/library/hh873177.aspx) on the subject that was very helpful to me. – Jimi Feb 07 '16 at 10:55
  • This is very nice to use sync methods in async mode. Thank you so much! – Marco Concas May 11 '19 at 14:21
16

If you didn't want to use async/await inside your method, but still "decorate" it so as to be able to use the await keyword from outside, TaskCompletionSource.cs:

public static Task<T> RunAsync<T>(Func<T> function)
{ 
    if (function == null) throw new ArgumentNullException(“function”); 
    var tcs = new TaskCompletionSource<T>(); 
    ThreadPool.QueueUserWorkItem(_ =>          
    { 
        try 
        {  
           T result = function(); 
           tcs.SetResult(result);  
        } 
        catch(Exception exc) { tcs.SetException(exc); } 
   }); 
   return tcs.Task; 
}

From here and here

To support such a paradigm with Tasks, we need a way to retain the Task façade and the ability to refer to an arbitrary asynchronous operation as a Task, but to control the lifetime of that Task according to the rules of the underlying infrastructure that’s providing the asynchrony, and to do so in a manner that doesn’t cost significantly. This is the purpose of TaskCompletionSource.

I saw it's also used in the .NET source, e.g. WebClient.cs:

    [HostProtection(ExternalThreading = true)]
    [ComVisible(false)]
    public Task<string> UploadStringTaskAsync(Uri address, string method, string data)
    {
        // Create the task to be returned
        var tcs = new TaskCompletionSource<string>(address);

        // Setup the callback event handler
        UploadStringCompletedEventHandler handler = null;
        handler = (sender, e) => HandleCompletion(tcs, e, (args) => args.Result, handler, (webClient, completion) => webClient.UploadStringCompleted -= completion);
        this.UploadStringCompleted += handler;

        // Start the async operation.
        try { this.UploadStringAsync(address, method, data, tcs); }
        catch
        {
            this.UploadStringCompleted -= handler;
            throw;
        }

        // Return the task that represents the async operation
        return tcs.Task;
    }

Finally, I also found the following useful:

I get asked this question all the time. The implication is that there must be some thread somewhere that’s blocking on the I/O call to the external resource. So, asynchronous code frees up the request thread, but only at the expense of another thread elsewhere in the system, right? No, not at all.

To understand why asynchronous requests scale, I’ll trace a (simplified) example of an asynchronous I/O call. Let’s say a request needs to write to a file. The request thread calls the asynchronous write method. WriteAsync is implemented by the Base Class Library (BCL), and uses completion ports for its asynchronous I/O. So, the WriteAsync call is passed down to the OS as an asynchronous file write. The OS then communicates with the driver stack, passing along the data to write in an I/O request packet (IRP).

This is where things get interesting: If a device driver can’t handle an IRP immediately, it must handle it asynchronously. So, the driver tells the disk to start writing and returns a “pending” response to the OS. The OS passes that “pending” response to the BCL, and the BCL returns an incomplete task to the request-handling code. The request-handling code awaits the task, which returns an incomplete task from that method and so on. Finally, the request-handling code ends up returning an incomplete task to ASP.NET, and the request thread is freed to return to the thread pool.

Introduction to Async/Await on ASP.NET

If the target is to improve scalability (rather than responsiveness), it all relies on the existence of an external I/O that provides the opportunity to do that.

Pang
  • 9,564
  • 146
  • 81
  • 122
Alberto
  • 783
  • 1
  • 7
  • 15
  • 1
    If you will see the comment above the [Task.Run implementation](https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs,298c51c20d5a4fad) e.g. _Queues the specified work to run on the ThreadPool and returns a Task handle for that work_. You actually do the same. I'm sure that Task.Run will be better, cause it internally manage CancelationToken and makes some optimizations with scheduling and run options. – unsafePtr Jun 06 '17 at 12:06
  • so what you did with ThreadPool.QueueUserWorkItem could be done with Task.Run , right? – Razvan Apr 21 '20 at 10:55
  • Its a best practice to use Task.Run instead of "do it yourself" via ThreadPool. Also, ThreadPool is an old school approach; hence the need to use TaskCompletionSource. – RashadRivera Sep 25 '20 at 17:47
8

One very simple way to make a method asynchronous is to use Task.Yield() method. As MSDN states:

You can use await Task.Yield(); in an asynchronous method to force the method to complete asynchronously.

Insert it at beginning of your method and it will then return immediately to the caller and complete the rest of the method on another thread.

private async Task<DateTime> CountToAsync(int num = 1000)
{
    await Task.Yield();
    for (int i = 0; i < num; i++)
    {
        Console.WriteLine("#{0}", i);
    }
    return DateTime.Now;
}
SalgoMato
  • 302
  • 2
  • 6
  • 1
    In a WinForms application the rest of the method will complete in the same thread (the UI thread). The `Task.Yield()` is indeed useful, for the cases that you want to ensure that the returned `Task` will not be immediately completed upon creation. – Theodor Zoulias Apr 06 '20 at 17:51