我有这样的界面
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方法。我读过他们应该只用于事件处理程序。这种情况下他们不好吗?如果是这样,为什么?
我担心的另一件事是更新循环的递归方式。但似乎因为它总是等待尚未完成的任务,所以它不会继续添加到调用堆栈。
答案 0 :(得分:4)
你应该使用[迭代]无限循环来创建无限循环而不是使用无限递归。
使用递归意味着不断花费精力在每次迭代时从头开始重新创建完全相同的状态机,而不是使用您已经拥有的完美的状态机,并且它不必要地混淆代码并降低清晰度(到了你自己甚至不确定可能产生的负面影响;你不希望每一个阅读代码的人都必须考虑同样的问题而没有真正的收获。此外,如果您希望能够将此方法中生成的异常传播给调用者(下面将进一步讨论),那么使用递归会产生许多问题,例如完全弄乱调用堆栈,从而实际上抛出所有这些异常水平很难,并且还会造成内存泄漏,因为每个“已完成”的状态机都无法清理。
对于void
方法,这并不是特别有问题。通常希望返回Task
的原因是您可以判断操作何时结束。你的操作永远不会完成,它们会永远运行在大多数情况下,获取永远不会完成的任务实际上并没有比完成任务更有用或更少。
唯一可能相关的方法是错误处理。如果你的循环产生错误并且方法是void
,那么它需要负责处理该错误本身,因为它在概念上是一种顶级方法。如果它返回一个Task
,那么它就可以简单地将该异常抛给它的调用者并让该调用者负责处理该异常。这将是不返回void
的唯一原因,因为该方法应该永远运行。