如何立即从异步循环返回值?

时间:2019-03-26 23:40:00

标签: c# wpf asynchronous async-await

我有一个异步发现功能,可以发现本地网络中的设备,单击按钮即可调用该设备。 我正在向所有设备发送广播消息,并使用CancellationTokenSource监听响应5秒钟。令牌过期后,我将返回一个IEnummerable的解析响应给我的WPF模型。

我想直接返回传入的响应(并在5秒钟后停止收听),以便我可以在UI中立即显示已切换的设备,而不是在5秒钟后全部显示。

这是我的代码:

public async Task<IEnumerable<IDevice>> Discover()
    {
        var client = new MyClient();
        var responseData = await GetResponseData(client);
        return this.ParseResponseData(responseData);
    }

    private IEnumerable<IDevice> ParseResponseData(List<DeviceResponseData> responseData)
    {
        foreach (var data in responseData)
        {
            yield return DeviceFactory.Create(data);
        }
    }

    private static async Task<List<DeviceResponseData>> GetResponseData(MyClient client, int timeout = 5000)
    {
        var cancellationToken = new CancellationTokenSource(timeout);
        var data = new List<DeviceResponseData>();

        // ... prepare message and send it
        await client.SendAsync(message, new CancellationToken());

        try
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                // Wait indefinitely until any message is received.
                var response = await client.ReceiveAsync(cancellationToken.Token);

                data.Add(new DeviceResponseData(/* ... */ response));
            }
        }
        catch (TaskCanceledException e)
        {

        }

        return data;
    }

2 个答案:

答案 0 :(得分:0)

还不清楚您要问的是什么...但是,如果您想显示输入的结果,可以通过多种方法来实现,例如去耦消息事件,等等。

但是,您可以只使用一个简单的Action

private static async Task<List<DeviceResponseData>> GetResponseData(MyClient client, Action<DeviceResponseData> update, int timeout = 5000)
{
   var cancellationToken = new CancellationTokenSource(timeout);
   ...
   while (!cancellationToken.IsCancellationRequested)
   {
      // Wait indefinitely until any message is received.
      var response = await client.ReceiveAsync(cancellationToken.Token);

      var result = new DeviceResponseData( /* ... */ response);

      data.Add(result);
      update(result);

   }
   ...
}

用法

var allResults =  await GetResponseData(client,data => UdpateUI(data), timeout);

注意 :因为这是异步等待模式,所以您不必将Action的结果编组回UI上下文,如果这是从

调用的地方

答案 1 :(得分:0)

如果您已升级到C#8,则可以选择返回IAsyncEnumerable。这种机制可以传播DeviceResponseData对象的流,这些对象一产生就可以被消耗。这是生产者方法,实现为iterator(包含yield条语句)。

private static async IAsyncEnumerable<DeviceResponseData> GetResponseDataStream(
    MyClient client, int timeout = 5000)
{
    using CancellationTokenSource cts = new CancellationTokenSource(timeout);

    // Prepare message...
    try
    {
        await client.SendAsync(message, cts.Token).ConfigureAwait(false);
    }
    catch (OperationCanceledException) { throw new TimeoutException(); }

    while (!cts.IsCancellationRequested)
    {
        Response response;
        try
        {
            // Wait until any response is received.
            response = await client.ReceiveAsync(cts.Token).ConfigureAwait(false);
        }
        catch (OperationCanceledException) { yield break; }

        yield return new DeviceResponseData(/* ... */ response);
    }
}

这是如何食用它:

await foreach (DeviceResponseData data in GetResponseDataStream(client))
{
    // Do something with data
}

最好的部分是,您不必在每次收到Dispatcher.Invoke对象时都添加同步代码(DeviceResponseData等)。异步/等待机制会自动为您恢复捕获的同步上下文(除非您通过使用ConfigureAwait方法告诉它执行其他操作)。

重要:请确保您disposeCancellationTokenSource创建的对象。