c# Can a "task method" also be an "async" method?
Asked Answered
S

2

25

I'm trying to get the hand of the new async CTP stuff and I'm probably confusing myself here.. I can have this "task method", with no problem:

    public static Task<String> LongTaskAAsync() {
        return Task.Run(() => {
            return("AAA");
            });
        }

But what if I need the task to execute another task, can I mark it as "async" and use "await"? I tried this:

public async static Task<String> LongTaskAAsync() {
        await Task.Delay(2000);
        return Task.Run(() => {
            return("AAA");
            });
        }

But then mysteriously get this compiler error: Since this is an async method, the return expression must be of type 'string' rather than Task<string>

What am I missing here?

Soucy answered 30/7, 2012 at 0:3 Comment(6)
Try using the return type string instead of Task<string>? (Just a guess.)Mccrary
Nah, then it complains that the "return of an async method must be void, Task, or Task<T>" !! which is ironic.. in my example above, the return is Task<String>.. I think that counts ask Task<T>, no?Soucy
okay, I'm beginning to see my own problems here. When the compiler sees "async Task" or "async Task<T>", it creates the task object "for you". In the case of Task<Int32>, for example, it creates a Task<Int32> for you and sets it to the return value of your method (which must be an int). So in my code above, which returns a Task, is not meeting the compilers expectation that I will return a String.Soucy
I'm not sure yet, but I think the answer to my original question of whether or not a task method (ie one that returns a Task object which (usually) represents a hot running async method) can also be an async method, I'm guessing the answer is no. I'm guessing the pattern expects that the methods that really launch async code (represented via a Task object that the developer caused to be created) is supposed to be just doing that asynch task.. and to string together multiple async tasks is achieved by usage of methods marked "async".Soucy
I think I know what you're getting at; see my updated answer.Dorseydorsiferous
Side note: Microsoft is recommending that you don't use Task.Run inside async methods.Flour
D
20

You may want to read my async/await intro post.

Return values from async methods are wrapped in a Task<TResult>. Likewise, await unwraps those return values:

public static async Task<String> LongTaskAAsync() {
  await Task.Delay(2000);
  return await Task.Run(() => {
    return("AAA");
  });
}

The reasoning behind this is described in my Async "Why Do the Keywords Work That Way" Unofficial FAQ.

P.S. You can also use Task.FromResult for simple tests like this.

Edit: If you want to create and return the Task object itself, then the method should not be async. One somewhat common pattern is to have a public non-async method that calls the async portion only if necessary.

For example, some kind of asynchronous cache - if the object is in the cache, then return it immediately; otherwise, asynchronously create it, add it to the cache, and return it (this is example code - not thread-safe):

public static Task<MyClass> GetAsync(int key)
{
  if (cache.Contains(key))
    return Task.FromResult(cache[key]);
  return CreateAndAddAsync(key);
}

private static async Task<MyClass> CreateAndAddAsync(int key)
{
  var result = await CreateAsync(key);
  cache.Add(key, result);
  return result;
}
Dorseydorsiferous answered 30/7, 2012 at 1:25 Comment(6)
I am reading your blog entries now, and they are answering so many questions that I had. Thank-you, Thank-you, Thank-you. As to my original question, I arrived at the same statement you make above: "If you want to create and return the Task object itself, then the method should not be async". I intuit why this is the case, but I'm struggling a bit putting it into words. I think it's hard to describe some of this stuff because the terms are so overloaded. In that very statement "If you want to return the Task object yourself".. there's abiguity: What exactly is "the task object"?Soucy
I mean the actual Task instance. You can either create it yourself (e.g., Task.FromResult or Task.Run or TaskCompletionSource), or you can have the compiler create one for you (e.g., the return value of an async method).Dorseydorsiferous
I mean, when I started studying this a few days ago, I though "The Task Object" was the object that one starts with Task.Run for example. (ie the actually piece of code someone has launched on another thread). But the return value of a method marked async can rightfully be called a "Task Object" too. That's what makes this pattern a little confusing. It's like when you first learn about filesystems and you wrap your head around how a directory is also a file, which contains other files. Sort of like that.Soucy
Yes, but some methods that return Tasks can be marked async, and other methods that return Tasks cannot be marked async. I'm trying to figure out how to describe this to the guys at work.Soucy
"Will the real Task Object please stand up?"Soucy
I follow these guidelines: 1) Every method is not async unless it needs to be async. 2) If you need the result of an asynchronous operation, then await it. 3) All methods containing await must be async (wrapping its result in Task<TResult> if necessary). Repeat (2) and (3) as necessary.Dorseydorsiferous
D
3

Can a “task method” also be an “async” method?

Yes it can be, by simply changing the method signature to public async static Task<Task<String>> LongTaskAAsync() since that is, what it will return.

If you use the async keyword, the runtime will wrap the type you return into a task, to enable asynchronousness. Say if you return a string, the runtime will wrap that into a Task<string>. int will go Task<int> and Task<string> will go Task<Task<string>>. See this console app to clearify:

public class Program
{
    public static void Main(string[] args)
    {
        // start the main procedure asynchron
        Task.Run(() => DoIt()).Wait();
    }

    // for async support since the static main method can't be async
    public static async void DoIt()
    {
        Program p = new Program();

        // use the methods
        string s = await p.GetString();
        int i = await p.GetInt();
        Task<string> tsk = await p.GetTaskOfString();

        // just to prove the task works:

        // C# 5
        string resultFromReturnedTask = await tsk;

        // C# 4
        string resultFromReturnedTask2 = tsk.Result;
    }

    public async Task<string> GetString()
    {
        return "string";
    }

    public async Task<int> GetInt()
    {
        return 6;
    }

    public async Task<Task<string>> GetTaskOfString()
    {
        return Task.Run(() => "string");
    }
}
Denature answered 30/7, 2012 at 1:23 Comment(2)
Thank-you for that. Point well taken. I think in my mind I've been trying to divvy up methods used in asyncrony as "producers" and "consumers". Producers are methods that really start a task on another thread (like via Task.Run) and return it. Producers cannot be marked async. Consumers on the other hand, use Producer methods with the await keyword. Consumers can also call other Consumers. Consumers must all be marked with async. This is how I saw the world until your post. Now I'm not so sure it's a useful way to think about all this. What is a useful way to think about all this? lolSoucy
We need a document "How to think about asynchrony"Soucy

© 2022 - 2024 — McMap. All rights reserved.