Search code examples
c#asynchronousmvvm

Creating/initializing task without starting it immediately


I want to create/initialize a Task object from a async Task method without starting it directly. When searching online I only find answer with Task created from void methods.

This is the task method I want to execute. As I need to do some web requests async, the method needs to be async. I want to do exception handling as well, so it can't be an async void.

private async Task WebRequestTask()
{
    try
    {
        string ResponseText = await httpClient.GetStringAsync("https://fakeurl.com");
        // process response code
    }
    catch (Exception ex)
    {
         // handle error
    }
}

This is my main method where I want to create the task:

private void StartTask()
{
    TokenSource = new CancellationTokenSource();
    Task RequestTask = ... // here I want to initialize the task without starting

    // chain continuation tasks to RequestTask

    RequestTask.Start();
}

I've tried following solutions but nothing answers my need:

Solution 1

Task RequestTask = new Task(WebRequestTask); 
Task RequestTask = WebRequestTask;

-> both cause a compiler error

Solution 2

Task RequestTask = Task.Run(WebRequestTask);

-> this start the task async and the current method continues (but here it could be possible an exception is thrown before the continuation tasks are chained)

Solution 3

Task RequestTask = WebRequestTask(); 

-> this start the task synchronously and chaining happens after the task is finished

Solutions 4

Task<Task> OuterTask = new Task<Task>(LoginToAzure);
await LoginAzureTask.Unwrap();

-> this start the outer task but the inner task is never called

How can I attach this Task method to an Task object, so that I can first attach continuation tasks/set some options and then start it? When it's possible, I'd like to use the cancellation token as well.


Solution

  • The asynchronous methods that are implemented with the async keyword are creating "hot" Tasks, in other words Tasks that are already started. You can defer the execution of an async method by wrapping it in another Task, thus creating a nested Task<Task>, like this:

    private void StartTask()
    {
        TokenSource = new CancellationTokenSource();
        var token = TokenSource.Token;
        Task<Task> taskTask = new Task<Task>(() => WebRequestAsync(token));
        Task task = taskTask.Unwrap(); // The task is not started yet
    
        // Chain continuations to task
    
        taskTask.RunSynchronously(TaskScheduler.Default);
        // The task is now started
    }
    
    private async Task WebRequestAsync(CancellationToken cancellationToken)
    {
        string responseText = await httpClient.GetStringAsync(
            "https://fakeurl.com", cancellationToken);
        // Process response text
    }
    

    The taskTask variable is the wrapper that represents just the execution of the WebRequestAsync method, not the completion of the Task that this method creates. The wrapper task is completed immediately after calling the RunSynchronously method, and after that point your code will have no use for it. The task variable "unwraps" the wrapper, and represents the actual asynchronous web request. It is completed when the asynchronous operation completes.

    As you can see this technique is rather cumbersome. You'll rarely see it in application code, because pure async-await composition is more convenient to use.