【问题标题】:Task await does not return the result任务等待不返回结果
【发布时间】:2017-06-02 13:42:39
【问题描述】:

我正在尝试编写一个使用 Task 和 TaskCompletion 的函数。

我的问题是登录后没有返回结果。我之前使用过类似的代码,它正在工作。我不知道造成这种情况的原因。

public async Task<byte[]> Sign(byte[] documentContent)
{
    var service = new SignServiceWrapper("https://example.com?wsdl");
    var loginResult = await Task.Run(() => service.Login(loginRequest));

    //....
}

还有我的 SignServiceWrapper 类

public class SignServiceWrapper
{
    private static string _webServiceUrl;
    private  BrokerClientClient client;

    public SignServiceWrapper(string webServiceUrl)
    {
        _webServiceUrl = webServiceUrl;
    }

    public Task<loginResponse> Login(loginRequest request)
    {
        var tcs = new TaskCompletionSource<loginResponse>();

        ClientGenerator.WebServiceUrl = _webServiceUrl;

        ClientGenerator.InitializeService(); 
        client = ClientGenerator.ServiceClient;   

        client.loginCompleted += (sender, loginResult) =>
        {
            if (loginResult.Error != null)
                tcs.SetException(loginResult.Error);
            else
                tcs.TrySetResult(loginResult.Result);
        };    

        client.loginAsync(request);                

        return tcs.Task;
    }

    // ...
}

如果我这样调用我的登录函数,它会起作用

var loginResult = Task.Run(() => service.Login(loginRequest));
loginResult.Wait();

我知道有一种死锁,但我不知道如何在这里解决这个问题以及哪个对象。

【问题讨论】:

  • 您等待任务完成多长时间了?您是否尝试过单步执行任务以确保它正在处理?
  • 我在 loginCompleted 事件中放了一个断点,结果出现了,但问题是它没有返回
  • await 没有损坏。您的代码虽然很不寻常 - Task.Run and TCS and Web 服务调用,已经有 loginAsync 方法?你为什么不删除 整个 Login 方法并调用 var response=await client.loginAsync(request);
  • 请注意,使用Task.Run 然后在其中进行阻塞调用会导致假异步。 WCF 的异步方法根本不使用线程。 Windows IO 实际上在内核级别是异步的,通过模拟阻塞来简化编程。在阻塞方法之上使用Task.Run 会浪费后台线程。使用 WCF 代理的 Async 或 Begin/End 方法不会
  • 我从 web 服务生成一个代理类,它只包含这样的异步调用。你提供的代码,loginAsync 是一个 void 方法,它不返回任何东西,所以我必须使用 loginCompleted 事件来获取结果.. 所以我必须使用 TaskCompletion

标签: c# async-await task deadlock


【解决方案1】:

更改var loginResult = await Task.Run(() =&gt;service.Login(loginRequest));var loginResult = await service.Login(loginRequest);

【讨论】:

  • 这可能还不够,因为Login 方法还混合了异步和非异步方法。注意 TCS loginAsync 方法。问题可能在ClientGenerator
【解决方案2】:

这是一个有效的.NET Fiddle

我认为你的.Login 方法做得太多了。我注意到的第一件事(并且只能想象它是如何实现的)是静态ClientGenerator,它具有静态可变状态。这是令人震惊的和非常具体的代码气味。我很想看看客户端本身是什么样子以及它是如何实现的,因为这肯定有助于更好地回答这个问题。

根据您迄今为止分享的内容(并假设 client.loginAsync 返回 Task&lt;loginResponse&gt;),我想说您可以执行以下操作:

public class SignServiceWrapper
{
    private static string _webServiceUrl;
    private  BrokerClientClient client;

    public SignServiceWrapper(string webServiceUrl)
    {
        _webServiceUrl = webServiceUrl;
    }

    public Task<loginResponse> LoginAsync(loginRequest request)
    {
        ClientGenerator.WebServiceUrl = _webServiceUrl;
        ClientGenerator.InitializeService();
        client = ClientGenerator.ServiceClient;

        return client.loginAsync(request);
    }

    // ...
}

然后你可以这样消费它:

public async Task<byte[]> Sign(byte[] documentContent)
{
    var service = new SignServiceWrapper("https://example.com?wsdl");
    var loginResult = await service.LoginAsync(loginRequest);

    //...
}

如果client.loginAsync 没有返回您要查找的内容,那么您需要通过something similar to your current approach 处理此问题。或者,如果您被锁定在 event-based async pattern,您还有其他考虑因素 - 例如您是否要支持取消、IsBusy、进度、增量结果,以及您是否有能力让事件 args 继承 @987654333 @等……

最后一个考虑因素,如果 client.loginAsyncTask 返回,即使它不返回 loginResponse 你也需要像这样等待它:

public async Task<loginResponse> Login(loginRequest request)
{
    var tcs = new TaskCompletionSource<loginResponse>();

    ClientGenerator.WebServiceUrl = _webServiceUrl;
    ClientGenerator.InitializeService();
    client = ClientGenerator.ServiceClient;
    client.loginCompleted += (sender, loginResult) =>
    {
        if (loginResult.Error != null)
            tcs.SetException(loginResult.Error);
        else
            tcs.TrySetResult(loginResult.Result);
    };    

    await client.loginAsync(request);                

    return tcs.Task;
}

更新

在与 OP 讨论后,.NET Fiddle 似乎符合他的需求。

【讨论】:

  • 感谢您的回答,但 loginAsync 方法是无效方法,因此 await 不起作用!好吧,我通过使用 like var loginResult = Task.Run(() => service.Login(loginRequest)).Result; 解决了这个问题;
  • 这不是解决问题,而是让自己陷入另一个僵局。你可以控制.loginAsync 和那个客户端类吗?
  • 是的,我知道这不是一个合适的解决方案 :) loginAsync 是一个 Web 服务功能,我无法访问该 Web 服务,因为它不是我们的项目,所以是外部源。
  • 我不想写基于事件的东西 :) 我一直在寻找用不同的方式解决问题..
  • 你看到我的.NET Fiddle了吗,这是正确的处理方式。
猜你喜欢
  • 1970-01-01
  • 2013-08-06
  • 1970-01-01
  • 2016-09-10
  • 1970-01-01
  • 2014-10-01
  • 1970-01-01
  • 2023-03-08
相关资源
最近更新 更多