0

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.

AnotherProgrammer
  • 535
  • 1
  • 6
  • 13
  • Look at the question, and my answer over here: https://stackoverflow.com/questions/44191716/when-executing-an-array-of-tasks-asynchronously-shouldnt-it-take-as-long-as-th/44193476#44193476. I think it describes what's going on. May help. – Clay May 26 '17 at 20:04
  • 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 May 26 '17 at 20:07
  • 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 May 26 '17 at 20:09
  • 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 May 26 '17 at 20:11

1 Answers1

2

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)

itsme86
  • 19,266
  • 4
  • 41
  • 57
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • Okay, so this was fairly helpful to view it from the perspective of events instead. Why does it seem so uncommon though? I see plenty of examples that do long running "practical" operations like reading from files or getting a response from a server asynchronously, do those also somehow rely on events? I would've thought they'd have to multithread those operations. Your example, while it meets my requirements, just seems somewhat useless since you can do it synchronously too by just storing a value on PrimeButton's click, and retrieving it on RunButton's click. – AnotherProgrammer May 26 '17 at 20:16
  • 1
    The practical examples also do not use threads (see the commend i said in the answer about disk and network IO) internally they are all using events from the OS to be told when the data they asked for is available, those events then get wrapped up in a TaskCompletionSource and that source is what is returned to the user.. Here is a useful article that kind of releates to what you are asking about "[There is no Thread](https://blog.stephencleary.com/2013/11/there-is-no-thread.html)" – Scott Chamberlain May 26 '17 at 20:40
  • Okay, so I read the article you provided which was helpful. In this case, it's not a thread that's used to run the I/O operation, because it's not the CPU doing it, but it's still a parallel operation because the disk is reading/writing at the same time as the CPU continues running. Is this correct? – AnotherProgrammer May 26 '17 at 20:51
  • Yes, that is the basic jist of it. – Scott Chamberlain May 26 '17 at 21:06