4

I found the following example from Microsoft website about Async and await ( https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/):

// Three things to note in the signature:  
//  - The method has an async modifier.   
//  - The return type is Task or Task<T>. (See "Return Types" section.)  
//    Here, it is Task<int> because the return statement returns an integer.  
//  - The method name ends in "Async."  
async Task<int> AccessTheWebAsync()  
{   
    // You need to add a reference to System.Net.Http to declare client.  
    HttpClient client = new HttpClient();  

    // GetStringAsync returns a Task<string>. That means that when you await the  
    // task you'll get a string (urlContents).  
    Task<string> getStringTask = client.GetStringAsync("https://msdn.microsoft.com");  

    // You can do work here that doesn't rely on the string from GetStringAsync.  
    DoIndependentWork();  

    // The await operator suspends AccessTheWebAsync.  
    //  - AccessTheWebAsync can't continue until getStringTask is complete.  
    //  - Meanwhile, control returns to the caller of AccessTheWebAsync.  
    //  - Control resumes here when getStringTask is complete.   
    //  - The await operator then retrieves the string result from getStringTask.  
    string urlContents = await getStringTask;  

    // The return statement specifies an integer result.  
    // Any methods that are awaiting AccessTheWebAsync retrieve the length value.  
    return urlContents.Length;  
}  

My first question is from the webpage "If AccessTheWebAsync doesn't have any work that it can do between calling GetStringAsync and awaiting its completion, you can simplify your code by calling and awaiting in the following single statement." It seems to imply that the following statement is really doing something:

    Task<string> getStringTask = client.GetStringAsync("https://msdn.microsoft.com");  

Is that true? I though it's just a definition of a task, but it has not yet started/run.

Second question is I tried to change the implementation as follows to check my idea:

async Task<int> AccessTheWebAsync()  
{    
    HttpClient client = new HttpClient();  

    //Task<string> getStringTask = client.GetStringAsync("https://msdn.microsoft.com");
        Task<string> getStringTask = DoPriorWork();  

    // You can do work here that doesn't rely on the string from GetStringAsync.  
    DoIndependentWork();  

    string urlContents = await getStringTask;  

    return urlContents.Length;  
}  
void DoIndependentWork()
    {
        resultsTextBox.Text += "Working . . . . . . .\r\n";
    }

    Task<string> DoPriorWork()
    {
        return new Task<string>(
            () =>
            {
                string retStr = "good";
                Debug.Assert(true, "running dopriorwork");
                System.Threading.Thread.Sleep(10000);
                return retStr;
            }
        );
    }

However, the method DoPriorWork() didn't execute at all. What is wrong with this implementation? Thanks!

Ames ISU
  • 167
  • 1
  • 2
  • 12
  • `I though it's just a definition of a task, but it has not yet started/run.` - it's the opposite. It has started at the point it is returned to you. But you have not awaited its result. – GSerg Nov 01 '18 at 18:10
  • Thanks GSerg for the comments. Can you answer my second question also? – Ames ISU Nov 01 '18 at 18:20
  • 1
    Do not mix Thread.Sleep (which blocks the thread) with async tasks, use Task.Delay instead, because that delays the task without blocking the thread. – Hans Kesting Nov 01 '18 at 18:33
  • @HansKesting The issue persists even with `static Task DoPriorWork() { return new Task(() => "good"); }`. It's a deadlock, but I cannot see why. – GSerg Nov 01 '18 at 18:35
  • try `async Task DoPriorWork() { await Task.Delay(10000); return "good";}` – Hans Kesting Nov 01 '18 at 18:38
  • 1
    GetStringAsync returns a started task. It is already running that is why when awaited it eventually returns. You created a unstarted task using the task constructor. Since it is not started it will never complete when awaited. The general understand is that APIs return started tasks and you shouldn't be using the task constructor unless you really know what you are doing. Use Task.Run instead. – Mike Zboray Nov 01 '18 at 18:38
  • Ah, the [`Task(Func)` constructor](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task-1.-ctor?view=netframework-4.7.2#System_Threading_Tasks_Task_1__ctor_System_Func__0__) returns an unstarted task. You need to `Start()` it manually. – GSerg Nov 01 '18 at 18:39

2 Answers2

1

Is that true? I thought it's just a definition of a task, but it has not yet started/run.

Indeed. The operation you called returned immediately, the task you're given is "hot" - i.e already running. That's why you get the task actually, so you can await it at a later point. Thus you get a way to run other stuff, as the example states.

Second question is I tried to change the implementation as follows to check my idea...

To be able to get the code running, try changing the the method DoPriorWork as follows:

private Task<string> DoPriorWork()
{
    return Task.Run(() =>
    {
        var retStr = "good";
        Console.WriteLine("running dopriorwork");
        Thread.Sleep(10000);
        return retStr;
    });
}

Note that instead of returning new Task instance, which would provide me with a Task in Created (not running) state, I'm queueing and returning a "hot" task, using the static method Task.Run().

Hope this helps!

Karel Tamayo
  • 3,690
  • 2
  • 24
  • 30
  • 2
    Use `Task.Delay` instead of sleep. See [related](https://stackoverflow.com/questions/20082221/when-to-use-task-delay-when-to-use-thread-sleep) – Hans Kesting Nov 01 '18 at 18:49
  • @HansKesting Form my understanding, I believe this doesn't matter in this case. If the OP uses `Thread.Sleep()` current thread will block whilst if uses Task.Delay() he must await that task until completion to achieve the pause. So not seeing much benefit in doing that. Am I wrong? – Karel Tamayo Nov 01 '18 at 18:58
  • Thanks Karel! but how do i know client.GetStringAsync("https://msdn.microsoft.com") is a "hot" task? I can not find the information about this through the metadata of HttpClient. – Ames ISU Nov 02 '18 at 22:16
0

From the same link, if you look at the diagram, you can follow the flow control.

For this line:

Task<string> getStringTask = client.GetStringAsync("https://msdn.microsoft.com"); 

Control flows into the GetStringAsync method (normal processing), and does not return to the caller until an await in that method is encountered, as specified in the diagram.

As for the statement:

"If AccessTheWebAsync doesn't have any work that it can do between calling GetStringAsync and awaiting its completion, you can simplify your code by calling and awaiting in the following single statement."

This refers to the DoIndependentWork() in the example which is executed before awaiting this defined getStringTask task.

This statement is saying, in the absence of the need for DoIndependentWork(), instead of:

Task<string> getStringTask = client.GetStringAsync("https://msdn.microsoft.com"); 
DoIndenpendentWork();
string urlContents = await getStringTask; 

you can do the following instead:

string urlContents = await client.GetStringAsync("https://msdn.microsoft.com"); 

Does that make sense?

sean
  • 367
  • 2
  • 12