使用async void实现数据提供程序

本文关键字:程序 数据 实现 async void 使用 | 更新日期: 2023-09-27 18:18:26

我有一个这样的界面

public interface IServerDataProvider 
{
    string Val1 { get; }
    string Val2 { get; }
    event EventHandler<EventArgs> Val1Changed;
    event EventHandler<EventArgs> Val2Changed;
}

它允许用户访问从服务器检索到的两个字符串,以及当这些字符串发生变化时触发的事件。

学习了c#中的async-await,我可以做一个相当简单的实现,定期检查这些值是否在服务器上被更改:
public class ServerDataProviderAsync : IServerDataProvider
{
    public event EventHandler<EventArgs> Val1Changed;
    public event EventHandler<EventArgs> Val2Changed;
    private string _val1Url = "someUrl";
    private string _val2Url = "otherUrl";
    private const int _delayMs = 1000;
    public ServerDataProviderAsync()
    {
        Start();
    }
    private async void Start()
    {
        Val1 = await DownloadString(_val1Url);
        Val2 = await DownloadString(_val2Url);
        Val1UpdateLoop();
        Val2UpdateLoop();
    }
    private async void Val1UpdateLoop()
    {
        await Task.Delay(_delayMs);
        Val1 = await DownloadString(_val2Url);
        Val1UpdateLoop();
    }
    private async void Val2UpdateLoop()
    {
        await Task.Delay(_delayMs);
        Val2 = await DownloadString(_val1Url);
        Val2UpdateLoop();
    }
    private string _val1;
    public string Val1
    {
        get { return _val1; }
        private set
        {
            if (_val1 != value && value != null)
            {
                _val1 = value;
                OnContentChanged(Val1Changed);
            }
        }
    }
    private string _val2;
    public string Val2
    {
        //similar to Val1
    }
    private async Task<string> DownloadString(string url)
    {
        using (var wb = new WebClient())
        {
            try { return await wb.DownloadStringTaskAsync(url); }
            catch { /*log error*/}
        }
        return null;
    }
    private void OnContentChanged(EventHandler<EventArgs> handler)
    {
        if (handler != null)
        {
            handler(this, EventArgs.Empty);
        }
    }
}

可以像这样在MainWindow中使用:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        var dataProvider = new ServerDataProviderAsync();
        //hook up to events and display strings in GUI
    }
}

现在我的问题是,这是不是一个好的实现?有没有更好的办法?

我担心的第一部分是异步void方法。我读到它们应该只用于事件处理程序。在这种情况下它们是坏的吗?如果是,为什么?

我担心的另一件事是更新循环的递归工作方式。但是,由于它总是等待尚未完成的任务,它似乎不会继续添加调用堆栈。

使用async void实现数据提供程序

你应该使用[迭代]无限循环来创建无限循环,而不是使用无限递归。

使用递归意味着不断地花费精力从头开始重新创建完全相同的状态机,而不是使用您已经拥有的完美状态机,并且它不必要地混淆了代码并降低了清晰度(甚至您自己都不确定可能的负面影响;您不希望每个阅读代码的人都不得不思考同样的问题,而没有真正的收获。此外,如果您希望能够将此方法中生成的异常传播给调用者(下面将进一步讨论),那么使用递归会有许多问题,例如完全弄乱调用堆栈,使实际抛出所有这些级别的异常变得困难,并且还会造成内存泄漏,因为每个"完成"的状态机都无法清理。

对于方法是void,这不是特别有问题。通常希望返回Task的原因是,这样您就可以知道操作何时结束。你的操作永远不会结束,它们永远在运行。在大多数情况下,得到一个永远不会完成的任务并不比根本没有得到一个任务更有用。

它可能相关的唯一方式是错误处理。如果你的循环产生了一个错误,而方法是void,它需要负责处理这个错误,因为它在概念上是一个顶级方法。如果它返回一个Task,那么它可以简单地将该异常抛出给它的调用者,并让调用者负责处理该异常。对于一个应该永远运行的方法,这是不返回void的唯一原因。