0
votes

I've been reading about asynchronous methods, specifically in C# with the new async/await keywords, and despite much reading and perusing this forum, I still am convinced that async requires multithreading. Please explain what I am misunderstanding!

I understand that you can write an async method without spawning a background thread. Super basic example:

async System.Threading.Tasks.Task<int> GetZeroAsync()
{
    return 0;
}

But of course, this method is completely useless to be marked as async because, well, it isn't asynchronous. I also get a compiler warning about the method lacking an "await" operator, as expected. Okay, so what can we await? I can await something like Task.Run(), but that defeats the point, because I'm now using multithreading. Any other example I've found online tries to prove that you don't need multithreading by simply doing something like so:

    async System.Threading.Tasks.Task<int> MyMethodAsync()
    {
        return await CallAnotherAsyncMethod();
    }

Maybe I'm misunderstanding this, but all it proves to me is that I'm not the one who starts the multithreaded task, but I'm just calling another method that does. Since CallAnotherAsyncMethod() is also an async method, it must follow the exact same rules, right?. I can't have every async method just await another async sub-method forever, at some point it must stop unless you want infinite recursion.

The way I currently understand it, and I know this is wrong, is that async doesn't use multithreading, but it does require it, otherwise it's just a synchronous method lying to you.

So here's what might help. If async truly does not require multithreading, the following situation must be producible, I just can't find a way to do it. Can somebody create an example of a method that follows these rules:

  1. Asynchronous.

  2. Actually runs asynchronously.

  3. Doesn't use any multithreading like calling Task.Run() or using a BackgroundWorker etc.

  4. Doesn't call any other Async methods (unless you can also prove that this method follows these rules too).

  5. Doesn't call any methods of the Task class like Task.Delay() (unless you can also prove that this method follows these rules too).

Any help or clarification would be really helpful! I feel like an idiot for not understanding this topic.

1
Look at the question, and my answer over here: stackoverflow.com/questions/44191716/…. I think it describes what's going on. May help. - Clay
It used Task.Delay to simulate long-running await on something I/O bound...like reading a boatload of data from a file or network. - Clay
I've probably seen that post before, but every example on there violates my rules, so it sadly doesn't help with my confusion. - AnotherProgrammer
Yeah - I get it. I had the same issues at first...will try to come up with something more obvious. It's really pretty slick once you get it. When you get the aha, you'll love it. - Clay

1 Answers

2
votes

The easiest example of an async operation that does not use any kind of threads is waiting for a event to happen.

Create a UI app with your framework of choice and have two buttons, one called PrimeButton and one called RunButton

private TaskCompletionSource<object> _event = new TaskCompletionSource<object>();

//You are only allowed to do async void if you are writing a event handler!
public async void PrimeButton_OnClick(object sender, EventArgs e)
{
    //I moved the code in to Example() so the async void would not be a distraction.
    await Example();
}

public async Task Example()
{
    await _event.Task;
    MessageBox.Show("Run Clicked");
}

public async void RunButton_OnClick(object sender, EventArgs e)
{
    _event.SetResult(null);
}

The await will wait till you click the 2nd button before it allows the code to continue and show the message box. No extra threads where involved at all here, all work was done using only the UI thread.

All a Task is, is a object that represents "something that will be finished at some point in the future". That something could be waiting for a background thread to complete that was started by a Task.Run or it could be waiting for a function to be called like the .SetResult( on the TaskCompletionSource<T>, or it could be waiting for some kind of disk or network IO to finish and be read in to a buffer by the OS (however internally this is usually implemented via a internal TaskCompletionSource<T> buried inside of the ReadAsync() function, so it is just a repeat of the last example with a wrapper around it)