【问题标题】:Hanging with WaitForConnectionAsync() on pipe [duplicate]在管道上挂起 WaitForConnectionAsync() [重复]
【发布时间】:2015-12-07 07:42:29
【问题描述】:

我正在通过命名管道与另一个进程通信。管道服务器用 C# 实现,客户端用 C 编写。服务器是 WPF 应用程序。

我需要创建一个NamedPipeServerStream 并等待(同步)最多 1 秒以供客户端连接。然后我需要知道客户端是否连接。

NamedPipeServerStream 取消/超时等待客户端连接的唯一方法是通过其异步 WaitForConnectionAsync 方法 - 它采用 CancellationToken - 我已经实现了我认为是同步等待的方法所以:

public bool WaitOneSecondForClientConnect()
{
    bool result = false;
    try
    {
        result = WaitForConnectionAsyncSyncWrapper().Result;
    }
    catch (AggregateException e)
    {
        log.Write("Error waiting for pipe client connect: " + e.InnerException.Message);
    }
    return result;
}

private async Task<bool> WaitForConnectionAsyncSyncWrapper()
{
    CancellationTokenSource cts = new CancellationTokenSource(1000);
    await pipe.WaitForConnectionAsync(cts.Token);
    return pipe.IsConnected;
}

管道定义如下:NamedPipeServerStream(pipeName, PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous, 1, 1); WaitOneSecondForClientConnect() 函数在 UI 线程上运行。

应该使其同步的是访问异步WaitForConnectionAsyncSyncWrapper() 函数的Result 属性,该属性位于它返回的Task&lt;bool&gt; 上。为了访问 Result,async 函数必须完全返回,并且它不能执行 return pipe.IsConnected 行,直到函数在 await pipe.WaitForConnectionAsync(cts.Token); 完成后恢复。至少这是我的理解。

所以问题是:尽管客户端程序说它打开了我的服务器的管道(当然在上面的代码执行之前已经创建了),WaitOneSecondForClientConnect() 永远不会返回。如果我闯入服务器,它就在这条线上:result = WaitForConnectionAsyncSyncWrapper().Result;

所以我猜它正在等待任务的结果可用,如果客户端在 1 秒内连接,它应该是 pipe.IsConnected 的值,或者如果 await 在我访问它时它应该抛出一个 AggregateException已完成,因为令牌已被取消(1 秒后)。但它只是完全挂起。

另一方面,如果我在令牌开始之前取消它,例如通过在调用await pipe.WaitForConnectionAsync(cts.Token);之前放置Thread.Sleep(2000);,然后连接被成功取消(我认为它甚至没有尝试启动,因为令牌已经被取消) - 访问Result属性会抛出AggregateException,等等……

有几点需要注意。

  • 如果我将WaitOneSecondForClientConnect() 的内容替换为 标准同步pipe.WaitForConnection();,每次都有效- 即客户端连接,函数返回。
  • 在我编写的一个测试程序中,最初让这个异步/同步的东西工作,以这种同步异步方式连接每次都能工作。它是一个控制台程序,而不是我真正的 WPF 程序。下面列出了测试程序的相关代码。
  • 上面发布的代码实际上已经运行了几次,可能失败了 30-40 次。
  • 如果我的客户端没有打开我的管道,我的“真实”代码仍然挂起,而我的测试代码等待指定的时间段,然后打印“连接失败”。正如预期的那样(见下文)——这正是我的真实代码中应该发生的行为。

有效的测试代码:

var pipe = new NamedPipeServerStream("SemiUsefulPipe_" + pid.ToString() + "ctest", PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous, 1, 1);

// ... dll containing pipe client is injected in client process at this point.

try
{
    var result = ConnectAsync(pipe).Result;
}
catch (AggregateException)
{
    Console.WriteLine("Connection failed.");
}

...

private static async Task<bool> ConnectAsync(NamedPipeServerStream pipe)
{
    CancellationTokenSource cts = new CancellationTokenSource(1000);
    await pipe.WaitForConnectionAsync(cts.Token);
    return pipe.IsConnected;
}

【问题讨论】:

  • 你没有正确使用 async/await
  • 也许不是——你能详细说明一下吗?
  • 你的问题太长了,我无法全盘考虑并制定答案
  • duplicate await vs Task.Wait - Deadlock? 解释了在 WPF/WinForms/ASP.Net 中调用 .Result 时的死锁。您的帖子很好地展示了您在寻找问题方面付出的努力 - 对此表示敬意。

标签: c# asynchronous pipe named-pipes


【解决方案1】:

您不能像这样混合使用异步和非异步方法。发生的事情是您的 WaitOneSecondForClientConnect 方法正在等待 WaitForConnectionAsyncSyncWrapper 方法完成。但是那个家伙需要它的调用线程是空闲的,这样它才能重新水合原始上下文。所以你刚刚创建了一个死锁。详情请见https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

相反,您需要一直保持异步。

public async Task<bool> WaitOneSecondForClientConnect()
{
    bool result = false;
    try
    {
        result = await WaitForConnectionAsyncSyncWrapper();
    }
    catch (Exception e)
    {
        log.Write("Error waiting for pipe client connect: " + e.Message);
    }
    return result;
}

【讨论】:

  • 太棒了,该链接向 T 描述了我的问题(包括为什么它在我的测试代码而不是我的 GUI 应用程序中有效)。现在几乎可以肯定有更好或更“正确”的方式来做这件事,因为我是 async/await 的新手;因此,为了使方法同步,我将代码保持原样,但通过将ConfigureAwait(false) 添加到await pipe.WaitForConnectionAsync(cts.Token) 的末尾来防止死锁,因此它会在线程池上执行剩余部分,并且可以正常工作!
猜你喜欢
  • 1970-01-01
  • 2012-09-14
  • 1970-01-01
  • 2011-10-21
  • 1970-01-01
  • 1970-01-01
  • 2020-04-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多