【问题标题】:TaskFactory inner task never get executed and always in WaitingForActivation statusTaskFactory 内部任务永远不会被执行并且总是处于 WaitingForActivation 状态
【发布时间】:2016-10-12 17:21:23
【问题描述】:

我正在使用 TaskFactory 使用以下代码启动新任务。

var task = Task.Factory.StartNew(async () =>
{
    await Task.Run(() =>
    {
        // Do API Call
        var saveResponse = doAPICall();
    }).ConfigureAwait(false);
}).Unwrap();

task.Wait();

doAPICall() 的代码如下所示,它调用了一个返回任务的外部 API。

private string doAPICall()
{
    Task<string> response = client.FindBags(request);
    response.Wait(60000);

    if (response.Status == TaskStatus.RanToCompletion)
    {
        return response.Result;
    }
}

问题出现在 doAPICall() 函数中,Task 'response' 状态永远不会更改为 RanToCompletion 并且始终处于 WaitingForActivation 状态。我已经尝试增加等待超时但仍然没有运气。我正在使用 TaskFactory 而不是 Task,因为将来我想创建自定义 TaskFactory 以更好地控制调度程序和并发。

这是我的代码中缺少的东西,内部任务永远不会被执行吗?

编辑 我根据删除不必要线程的评论修改了调用 doAPICall() 的代码,但仍然没有运气。 :-(

var task = Task.Factory.StartNew(() =>
{
    // Do API Call
        var saveResponse = doAPICall();
});

task.Wait();

【问题讨论】:

  • 为什么所有这些额外的任务东西,我很认真地问,因为你使用了更多的线程然后是必要的(超过 1 是不必要的)。如果您的 API 调用默认不是异步的,那么正确的做法是使用 TaskCompletionSource 并且不启动线程。
  • 我的外部 API 调用是一个返回任务的异步函数。
  • 您是否尝试从同步代码中调用异步代码?
  • doAPICall() 对外部 API 的函数调用是异步调用,而 doAPICall 是从 Task.Run 调用的
  • client.FindBags(request) 是返回任务,Findbags 是异步函数。

标签: c# .net task-parallel-library task taskfactory


【解决方案1】:

您目前正在使用 sync-over-fake-async-over-fake-async-over-sync-over-async。这真是一团糟。

遵循以下准则:

  1. 不要使用Task.Factory.StartNew。曾经。 I am using the TaskFactory instead of Task because of in future I want to create custom TaskFactory for more control over scheduler and concurrency. 自定义任务调度程序不适用于异步代码。您可能必须采用更异步的调度/并发解决方案。更多信息on my blog
  2. 不要阻塞异步代码。我看到两个Wait 电话和一个Result,这两个都是严重的危险信号。更多信息on my blog
  3. 不要在生产代码中使用Task.Status。可以进行调试,但您不必将其用于实际逻辑。总有更好的解决方案。更多信息on my blog
  4. 仅使用来自 UI 层的Task.Run,而不是嵌套在帮助程序/库代码中。换句话说,用它来调用方法,而不是实现方法。更多信息on my blog

从最里面的方法出:

private async Task<string> doAPICallAsync()
{
  Task<string> responseTask = client.FindBags(request);
  // Note: it would be far better to use a cancellation token here instead of a "timed wait".
  Task timeoutTask = Task.Delay(60000);
  Task completedTask = await Task.WhenAny(responseTask, timeoutTask);
  if (completedTask == responseTask)
    return await completedTask;
}

你的调用代码变成:

var saveResponse = await doAPICallAsync();

【讨论】:

    【解决方案2】:

    您将异步调用包装在一个同步调用中,然后再包装在两个异步调用中。太多了,只需使用一个异步调用就可以了。

    private Task<string> doAPICallAsync()
    {
        return client.FindBags(request);
    }
    

    如果您在client.FindBags(request) 之后需要做一些工作,请使用此版本:

    private async Task<string> doAPICallAsync()
    {
        var result = await client.FindBags(request);
        var someNewResult = //do something with result
        return someNewResult;
    }
    

    然后这样称呼它:

    var result = await doAPICallAsync();
    

    如果你真的需要同步版本

    private string doAPICall()
    {
        return client.FindBags(request).Result;
    }
    

    然后这样称呼它:

    var result = doAPICall();
    

    如果可以,不要忘记在每次等待后添加ConfigureAwait(false)

    【讨论】:

    • 感谢 wertzui,但一旦结果到达,我需要在 doAPICall() 函数中进行大量字符串操作。所以我可以使用您的同步版本并在 Task.Factory.StartNew 中调用 doAPICall()。我尝试了相同的方法,但 client.FindBags(request).Result 将导致默认超时,并且抛出 Task was cancelled 异常。
    • 我在client.FindBags(request) 之后添加了一个版本,您可以使用它在doAPICallAsync() 内部做一些工作
    猜你喜欢
    • 2018-02-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-07
    • 1970-01-01
    • 2021-08-05
    • 2013-06-28
    • 2012-03-04
    相关资源
    最近更新 更多