【问题标题】:Await for Task in C# not returing等待 C# 中的任务不返回
【发布时间】:2020-08-31 20:57:28
【问题描述】:

我对以下代码有疑问。在 Debug 它运行到 }); return true 之后的部分,但从不返回调用方法。以下方法应连接到斑马打印机,发送 ZLP 代码打印并检查状态以确保打印成功。然后返回 true 或 false(之后将添加异常处理),如果为 true,则将打印的值提交到数据库。

private Boolean printLabel(string productName, double productWeight, String customerLabelType, String scaleNumber, String packageDate, String lotNumber, String barCode)
{
    string s = dao.getCustomerLabel(customerLabelType);
    s = s.Replace("<<<product_weight>>>", productWeight.ToString());
    s = s.Replace("<<<product type>>>>", productName);
    s = s.Replace("<<<barcode>>>", barCode);
    s = s.Replace("<<<Packed Date:>>>", packageDate);
    s = s.Replace("<<<Lot Number:>>>", lotNumber);
    //  RawPrinterHelper.SendStringToPrinter("ZDesigner GX420d", s);
    string ipAddress = dao.getPrinterIp(scaleNumber);

    try
    {
        return PrintOneLabelTask(ipAddress, s).Result;
    }
    catch (Exception ex)
    {

    }

    return false;
}

private async Task<Boolean> PrintOneLabelTask(string theIpAddress, String ZPL_STRING)
{             
    await Task.Run(() =>
    {
        // Instantiate connection for ZPL TCP port at given address
        Connection thePrinterConn = new TcpConnection(theIpAddress, TcpConnection.DEFAULT_ZPL_TCP_PORT);

        ZebraPrinter printer = PrintHelper.Connect(thePrinterConn, PrinterLanguage.ZPL);
        PrintHelper.SetPageLanguage(printer);
        if (PrintHelper.CheckStatus(printer))
        {
            PrintHelper.Print(printer, ZPL_STRING);
            if (PrintHelper.CheckStatusAfter(printer))
            {
                Console.WriteLine($"Label Printed");
            }
        }
        printer = PrintHelper.Disconnect(printer);
        Console.WriteLine("Done Printing");
        return true;
    });

    return false;
}

【问题讨论】:

  • PrintOneLabelTask 中使用await Task.Run(() 的确切目的是什么?您正在使用 async-over-sync 来冻结 .Result; 的线程。您可以删除该 Task.Run (以这种方式安排 I/O 工作无用)或一直异步(并使 printLabel 也异步,及其调用者)。此外,您描述的情况称为 deadlock
  • .Result 可能出现死锁。
  • 是的,感谢我在修改代码后添加了 return Task.Run(async () =&gt; await PrintOneLabelTask​​(ipAddress, s)).Result; 并将 PrintOneLabelTask​​ 修改为异步并删除了等待

标签: c# asynchronous async-await task


【解决方案1】:

当您从 lambda 表达式中return 时,您将退出 lambda 表达式并将控制权返回给 PrintOneLabelTask。您根本没有返回来自 PrintOneLabelTask。因此,在您的代码中,PrintOneLabelTask 方法将始终返回 false。

您需要捕获结果并将其返回。

private async Task<Boolean> PrintOneLabelTask(string theIpAddress, String ZPL_STRING)
{
    bool result = false;
     
    await Task.Run(() =>
    {
        // Instantiate connection for ZPL TCP port at given address
        Connection thePrinterConn = new TcpConnection(theIpAddress, TcpConnection.DEFAULT_ZPL_TCP_PORT);

        ZebraPrinter printer = PrintHelper.Connect(thePrinterConn, PrinterLanguage.ZPL);
        PrintHelper.SetPageLanguage(printer);
        if (PrintHelper.CheckStatus(printer))
        {
            PrintHelper.Print(printer, ZPL_STRING);
            if (PrintHelper.CheckStatusAfter(printer))
            {
                Console.WriteLine($"Label Printed");
            }
        }
        printer = PrintHelper.Disconnect(printer);
        Console.WriteLine("Done Printing");
        result = true;
    });

    return result;
}

注意:您的代码中可能还有其他一些问题...例如,我没有看到任何可能的情况下该方法将返回 false。

【讨论】:

  • 虽然这是真的,但问题似乎是在调用方法中由.Result 调用引起的死锁:“......但永远不会返回调用方法”
  • 或者更简单的return await Task.Run(() =&gt;...
【解决方案2】:

您似乎遇到了由框架的SynchronizationContext 引起的死锁。

当您访问从PrintOneLabelTask 返回的Task&lt;bool&gt; 中的Result 时,您将阻塞当前线程,直到该任务完成。

只要您点击await Task.Run(() =&gt;Task&lt;bool&gt; 就会返回:Task.Run 会返回另一个 Task,一旦提供的委托在 ThreadPool 上运行完成,该Task 就会完成。

但是,如果您的 SynchronizationContext 将延续安排回调用线程,它将尝试在已被 Result 阻塞的线程上恢复 PrintOneLabelTask

这是你的死锁。


解决方案

PrintOneLabelTask 根本不是真正的异步;它是在 ThreadPool 上运行的同步方法。

这本身不是问题,但它可能会导致代码无法维护,出现您当前遇到的问题。

方法应该写成同步操作:

private void PrintOneLabel(string theIpAddress, string ZPL_STRING)
{             
    // Instantiate connection for ZPL TCP port at given address
    Connection thePrinterConn = new TcpConnection(theIpAddress, TcpConnection.DEFAULT_ZPL_TCP_PORT);

    ZebraPrinter printer = PrintHelper.Connect(thePrinterConn, PrinterLanguage.ZPL);
    PrintHelper.SetPageLanguage(printer);
    if (PrintHelper.CheckStatus(printer))
    {
        PrintHelper.Print(printer, ZPL_STRING);
        if (PrintHelper.CheckStatusAfter(printer))
        {
            Console.WriteLine($"Label Printed");
        }
    }
    printer = PrintHelper.Disconnect(printer);
    Console.WriteLine("Done Printing");
}

(该方法也应该是void而不是bool,因为没有可以返回false的情况。

然后同步调用方法:

try
{
    PrintOneLabel(ipAddress, s);
    return true;
}
catch (Exception ex)
{

}

return false;

如果您正在构建一个具有 UI 线程的应用,而这现在会导致您的 UI 冻结,那么请决定在可能的最高级别卸载到 ThreadPool(通常是事件处理程序):

public async void YourEventHandler(object sender, EventArgs e)
{
    //...
    bool result = await Task.Run(() => printLabel(//...
    //...
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-06-02
    • 1970-01-01
    • 1970-01-01
    • 2016-09-10
    • 2014-10-01
    • 2015-10-25
    • 1970-01-01
    相关资源
    最近更新 更多