等待任务在完成后没有返回结果

12

我的问题在于一个任务完成了,但没有返回。我有一个网站和一个位于不同服务器上的Web服务。该网站调用Web服务,后者利用具有myFunction()函数的库。如果我从Web服务的控制台应用程序中调用myFunction,则会按预期返回。然而,当我从网站调用Web服务以调用myFunction()时,它将到达“步骤3”,但不是“步骤4”。下面是调用的简化版本。

private string myFunction()
{
    string myStr = myDoWork().GetAwaiter().GetResult();

    return myStr;
}

private async Task<string> myDoWork()
{
    logger.Debug("Step 1");
    string answer = await aFunction();

    logger.Debug("Step 4");

    return answer;
}

public async Task<string> aFunction()
{
    logger.Debug("Step 2");
    return await bFunction(CancellationToken.None);
}

AsyncLock myLock = new AsyncLock();
public Task<string> bFunction(CancellationToken cToken)
{
    return Task.Run(
        async () =>
        {
            using (await myLock(cToken))
            {
                logger.Debug("Step 3");
                result = "Hello";
                return result;
            }
        },
        cToken);
}

我对async和await不太熟悉,希望能得到帮助。


如果可能的话,myFunction() 也应该是异步的。如果在异步调用上阻塞,可能会发生奇怪的事情。另外,为什么在 bFunction 中使用 Task.Run - Nate Barbettini
这里没有任何理由 Step 4 不会被执行。 - Jonesopolis
@NateBarbettini,我不知道为什么要使用Task.Run。我尽可能地简化了代码,但你提到的一段可能很重要。我正在重用为异步任务编写的代码来执行同步任务。如果要在没有异步内容的情况下重新编写所有内容将是一个相当大的工作量。 - Jesse McConahie
一般来说,混合使用同步和异步是一个不好的主意。理想的解决方案是将其全部改为异步或同步。除此之外,我没有看到你的示例中有任何其他问题,只是你可以摆脱 Task.Run(async () =>{ }) 来简化这部分代码。 - Nate Barbettini
2
很可能你遇到了死锁问题,因为你将同步和异步代码混合。.GetAwaiter().GetResult()会同步等待任务完成。请参考http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html获取更多信息。 - Yacoub Massad
1个回答

12

几乎可以确定是 myFunction() 发生了死锁。第4步计划在主线程上执行,但无法执行,因为主线程被阻塞等待 GetResult()

应该接近下面这样:

private string myFunction()
{
    // This is NOT good style - you should avoid blocking on the result of a task.
    string myStr = Task.Run(async () => await myDoWork()).GetAwaiter().GetResult();
    return myStr;
}

private async Task<string> myDoWork()
{
    logger.Debug("Step 1");
    string answer = await aFunction();
    logger.Debug("Step 4");
    return answer;
}

public Task<string> aFunction()
{
    logger.Debug("Step 2");
    return bFunction(CancellationToken.None);
}

AsyncLock myLock = new AsyncLock();
public async Task<string> bFunction(CancellationToken cToken)
{
    using (await myLock(cToken))
    {
        logger.Debug("Step 3");
        return "Hello";
    }
}

一般来说,应该从可能的最高级别调用Task.Run()。尽可能保持同步,并让调用方决定是否要使用后台线程。


非常感谢!这修好了。 - Jesse McConahie
3
更好的解决方案是修复您的代码,使myFunction成为异步函数,并且不使用Task.Run.GetResult() - Scott Chamberlain
@ScottChamberlain 我同意。 - Jesse McConahie
今天我也遇到了同样的问题,虽然上面的解决方案对我有用,但后来我发现使用像 await _client.PostAsJsonAsync(apiPath, content).ConfigureAwait(false); 这样的 ConfigureAwait(false) 也可以解决问题。@scottChamberlain 在进行异步调用时使用 ConfigureAwait(false) 是否可以?作为任务编程的新手,如果有人能在这里提供您的意见,我将不胜感激。 - mabiyan
@Mabiyan 只要 函数的其余部分可以安全地在后台运行,使用 ConfigureAwait(false) 是可以的。唯一不安全的情况是在 await 之后更新 UI。 - piedar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接