Creating/initializing 任务没有立即开始

Creating/initializing task without starting it immediately

我想从异步任务方法 create/initialize 任务对象而不直接启动它。在线搜索时,我只找到从 void 方法创建的任务的答案。

这是我要执行的任务方法。因为我需要做一些异步的网络请求,所以该方法需要是异步的。我也想做异常处理,所以它不能是 async void.

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

这是我要创建任务的主要方法:

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

    // chain continuation tasks to RequestTask

    RequestTask.Start();
}

我已经尝试了以下解决方案,但没有满足我的需求:

解决方案 1

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

-> 两者都会导致编译器错误

解决方案 2

Task RequestTask = Task.Run(WebRequestTask);

-> 这将启动任务异步并且当前方法继续(但这里可能会在链接继续任务之前抛出异常)

解决方案 3

Task RequestTask = WebRequestTask(); 

-> 这会同步启动任务,并在任务完成后发生链接

解决方案 4

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

-> 这会启动外部任务,但永远不会调用内部任务

如何将此 Task 方法附加到 Task 对象,以便我可以先附加延续 tasks/set 一些选项,然后启动它?如果可能的话,我也想使用取消令牌。

I want to create/initialize a Task object from a async Task method without starting it directly.

最好的方法是使用委托。例如:

private void StartTask()
{
  TokenSource = new CancellationTokenSource();
  Func<Task> taskFactory = () => WebRequestTask(TokenSource);

  // chain continuation tasks

  Task task = taskFactory(); // Start the task
  ... // await the task or save it somewhere   
}

旁注:我强烈建议使用 await 而不是 ContinueWith 来实现“链延续任务”。 await 是一种干净安全的方法; ContinueWith 是一种低级、危险的方法。

使用 async 关键字实现的异步方法正在创建“热”Task,换句话说,Task 已经启动。您可以通过将 async 方法包装在另一个 Task 中来延迟其执行,从而创建嵌套的 Task<Task>,如下所示:

{
    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)
{
    try
    {
        string responseText = await httpClient.GetStringAsync(
            "https://fakeurl.com", cancellationToken);
        // Process response code
    }
    catch (Exception ex)
    {
        // Handle error
    }
}

taskTask 变量是仅表示 WebRequestAsync 方法执行的包装器,而不是此方法创建的 Task 的完成。包装器任务在调用RunSynchronously method, and after that point your code will have no use for it. The task variable "unwraps包装器后立即完成,代表实际的异步Web请求。当异步操作完成时完成。

如您所见,此技术相当麻烦。你很少会在应用程序代码中看到它,因为纯异步等待组合使用起来要方便得多。

I want to do exception handling as well, so it can't be an async void.

因为您正在处理 WebRequestTask() 内部的错误,所以这并不重要。

首选选项是将 StartTask 设置为 async Task,但如果不可能,请将其设置为 async void。这也满足了“不启动”的要求。

所以,保持简单:

private async void StartTask()
{
    TokenSource = new CancellationTokenSource();

    try
    {
      await WebRequestTask(TokenSource.Token);

      // here I want to initialize the task without starting -- irrelevant in an async void

      await otherTask();  // continuation(s)
    }
    catch()
    {
       // handle residual errors
    }   
}