使用多个异步调用的结果填充ObservableCollection,而无需等待

时间:2018-11-08 13:31:31

标签: c# mvvm async-await task-parallel-library observablecollection

在OnNavigatedTo方法的ViewModel类的Prism模块中

我想用多个异步调用的结果填充ObservableCollection,而不必等待所有调用完成

我正在使用此问题的答案: How to hydrate a Dictionary with the results of async calls?

以下代码是我的真实代码的清理版本:

我的状态等级:

public class Status
{
    public string ipAddress;
    public string status;
}

我的视图模型:

using Prism.Mvvm;
using Prism.Regions;

public class StatusViewModel : BindableBase, INavigationAware
{
    ObservableCollection<Status> statusCollection = new ObservableCollection<Status>();
    HttpClient httpClient = new HttpClient();
    Ping ping = new Ping();
    List<string> ipAddressList = new List<string>();

    public void OnNavigatedTo(NavigationContext navigationContext)
    {
        GetEveryStatus();
    }

    public void GetEveryIp() // this is not important, works ok
    {
        var addressBytes = Dns.GetHostEntry(Dns.GetHostName()).AddressList.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork).GetAddressBytes();
        for (byte i = 1; i < 255; ++i)
        {
            addressBytes[3] = i;
            string ipAddress = new IPAddress(addressBytes).ToString();
            if (ping.Send(ipAddress, 10).Status == IPStatus.Success)
            {
                ipAddressList.Add(ipAddress);
            }
        }
    }

    public void GetEveryStatus() // this is important, here is the problem
    {
        GetEveryIp();
        var task = GetStatusArray(ipAddressList);
        statusCollection.AddRange(task.Result);
    }

    // solution from stackoverflow, but it throws exception
    public async Task<Status[]> GetStatusArray(List<string> ipAddressList)
    {
        Status[] statusArray = await Task.WhenAll(
            ipAddressList.Select(
                async ipAddress => new Status(
                    ipAddress,
                    await httpClient.GetStringAsync("http://" + ipAddress + ":8080" + "/status")
                    )
                )
            );

        return statusArray;
    }
}

但这没用,因为GetStringAsync可以抛出异常,所以我将其更改为:

    public void GetEveryStatus() // this is important, here is the problem
    {
        GetEveryIp();
        foreach (string ipAddress in ipAddressList)
        {
            try
            {
                var task = httpClient.GetStringAsync("http://" + ipAddress + ":8080" + "/status");
                statusCollection.Add(new Status(ipAddress, task.Result));
            }
            catch (Exception)
            {
            }
        }
    }

但是它仍然不起作用。

正确的方法是什么?谢谢!

1 个答案:

答案 0 :(得分:1)

感谢@AccessDenied解释了async在接口实现中的作用。

感谢@Selvin解释Task.ResultTask.Wait

如果有人对最终解决方案感兴趣,请联系:

PositioningModule 是硬件设备,与此类无关 Prism.Modularity.IModule

public class PositioningModule
{
    public string IpAddress { get; set; }
    public PositioningModuleStatus PositioningModuleStatus { get; set; }

    public PositioningModule(string ipAddress, PositioningModuleStatus positioningModuleStatus)
    {
        IpAddress = ipAddress;
        PositioningModuleStatus = positioningModuleStatus;
    }
}

ViewModel:

我必须在BindingOperations.EnableCollectionSynchronization上使用lockObservableCollection这是async之前不起作用的主要原因!

OnNavigatedTo更改为async会阻止用户界面,因此我使用了Task.Run()

using Prism.Mvvm;
using Prism.Regions;

public class DomePositioningViewModel : BindableBase, INavigationAware
{
    ObservableCollection<PositioningModule> _positioningModuleCollection = new ObservableCollection<PositioningModule>();
    readonly object _lock = new object();

    DomePositioningModel _domePositioningModel = new DomePositioningModel();

    public DomePositioningViewModel()
    {
        BindingOperations.EnableCollectionSynchronization(_positioningModuleCollection, _lock);
    }

    public /* async */ void OnNavigatedTo(NavigationContext navigationContext)
    {
        //await _domePositioningModel.ScanForModulesAsync(AddModule); - this blocks the UI

        Task.Run(() => _domePositioningModel.ScanForModulesAsync(AddModule));
    }

    private void AddModule(PositioningModule module)
    {
        lock (_lock)
        {
            _positioningModuleCollection.Add(module);
        }
    }
}

模型:

我将Send更改为SendPingAsync,不得不使用new Ping()而不是ping

使用Select而不是foreach进行并行调用使一切变得更快!

#define PARALLEL

public class DomePositioningModel
{
    private readonly HttpClient _httpClient = new HttpClient();

    public DomePositioningModel()
    {
        _httpClient.Timeout = TimeSpan.FromMilliseconds(50);
    }

    public async Task ScanForModulesAsync(Action<PositioningModule> AddModule)
    {
        List<string> ipAddressList = new List<string>();

        var addressBytes = Dns.GetHostEntry(Dns.GetHostName()).AddressList.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork).GetAddressBytes();

        for (addressBytes[3] = 1; addressBytes[3] < 255; ++addressBytes[3])
        {
            ipAddressList.Add(new IPAddress(addressBytes).ToString());
        }

        //Ping ping = new Ping(); - this behaves strangely, use "new Ping()" instead of "ping"

#if PARALLEL
        var tasks = ipAddressList.Select(async ipAddress => // much faster
#else
        foreach (string ipAddress in ipAddressList) // much slower
#endif
        {
            PingReply pingReply = await new Ping().SendPingAsync(ipAddress, 10); // use "new Ping()" instead of "ping"

            if (pingReply.Status == IPStatus.Success)
            {
                try
                {
                    string status = await _httpClient.GetStringAsync("http://" + ipAddress + ":8080" + "/status");

                    if (Enum.TryParse(status, true, out PositioningModuleStatus positioningModuleStatus))
                    {
                        AddModule?.Invoke(new PositioningModule(ipAddress, positioningModuleStatus));
                    }
                }
                catch (TaskCanceledException) // timeout
                {
                }
                catch (HttpRequestException) // could not reach IP
                {
                }
                catch (Exception ex)
                {
                    System.Windows.MessageBox.Show(ex.Message);
                }
            }
        }
#if PARALLEL
        );
        await Task.WhenAll(tasks);
#endif
    }
}

之所以没有进行基准测试,是因为差异如此明显-大约是0.5秒而不是14秒!