【问题标题】:dispatcher invoke async await .net 4.6.1调度程序调用异步等待.net 4.6.1
【发布时间】:2019-04-28 02:41:16
【问题描述】:

我的目标是 WPF .NET 4.6.1。 我经常这样做:

btnClick
{
Task T1 = Task.Run<List<Double>>( AsyncFunction );
T1.Wait(.1);
Dispatcher.Invoke(() => { txtStatus.Text+="HOLD ON.."; };
T1.Wait(.1);
}

在这些 Wait(s) 之间真正更新 UI 的唯一方法是:

Dispatcher.Invoke(new Action(()=>{}),priority: DispatcherPriority.ApplicationIdle);

这是对的吗?这是便携的吗?描述讨论不同平台(UWP、Windows 10 Phone)上的不同方法。

可能会见Raymond Chen:https://devblogs.microsoft.com/oldnewthing/20190327-00/?p=102364

我明确不想使用异步按钮事件,因为我需要能够帮助用户,至少将逻辑用于十分之一秒的完成检查,并且不让用户猛烈抨击异步按钮处理程序试图获得结果并让我的传感器网络充满数据。

你知道我最终也想得到这个结果,并在相同的事件处理程序代码中处理它,所以就像我不想使用 BackroundWorker 然后让用户必须单击另一个按钮来轮询结果,可能仍然是空的,让每个人都非常非常生气。而且我不想自动轮询结果然后再次轮询传感器网络并导致更多拥塞。

【问题讨论】:

  • 这不是“正确的”,因为该方法似乎比使用异步等待更糟糕。如果您想在按钮开始的漫长过程完成之前避免第二次单击,可能会带有一些繁忙的标志。如果这就是要求。
  • @Andy 我正在使用异步等待,我正在执行任务运行,并且大多数情况下我只是在我的函数中等待网络数据。我的问题是关于调度员的。我认为我可以调用并且 UI 线程会在我的同步等待调用之间的任何上下文切换期间为我更新文本框。但直到我完全退出按钮点击事件。或者做那些无用的事情。

标签: wpf async-await


【解决方案1】:

我明确不想使用异步按钮事件,因为我需要能够帮助用户,至少将逻辑用于十分之一秒的完成检查,并且不让用户猛烈抨击异步按钮处理程序以获取结果并让我的传感器网络充满数据。

最简单的解决方案是按照其他人解决问题的方式进行:在操作进行时禁用按钮。

async btnClick
{
  btn.Enabled = false;
  try
  {
    txtStatus.Text += "HOLD ON..";
    var result = await Task.Run(() => AsyncFunction());
    ... // update UI with result.
  }
  finally { btn.Enabled = true; }
}

【讨论】:

  • 好吧,有可能,我实际上不想这样做,因为某些操作员我仍然希望他们能够按 Button 4 或 5 次。但这并不能真正回答我的问题,我需要了解 Empty Action 委托和 Dispatcher Priority Idle
  • 这是安迪兹的好建议。通常这将是一个命令而不是单击处理程序。您的帖子似乎表明在处理仍在进行时停止用户第二次点击是必要的。这是通常的。现在你说有时你不想那样做。这是不寻常的。听起来有点冒险。
  • @Andy 我需要在同步代码中做出决定。我需要往返时间和返回的次数,这样我就可以在大多数情况下决定大多数操作员——限制传感器请求——但通常我只想在 50 毫秒网络等待之间更新 UI。就像一旦我决定限制我需要立即开始一个新请求,计时,紧急更新 UI,然后如果用户不只是坐在那里猛烈投票,那么可能会启动一个后台队列来收集结果。所以我需要调度员工作。而且我不想返回异步按钮并放弃绘制 UI。
  • @AndyzSmith:我认为 System.Reactive 会是一个更好的解决方案。过去,我使用async 来驯服 20Hz 设备输入,但我认为 Rx 会更干净(当时它不存在)。无论如何,dispatcher 和Task.Wait 是在这里使用的错误工具。
  • 我还是看不出同步控制的必要性。请记住,Windows 并不是一个实时操作系统。 100ms 等待是可以的,但肯定不精确;我希望从 80 到 120 毫秒唤醒。 Dispatcher 的可靠性更差,因为它取决于您的应用当时收到多少 Win32 消息。
【解决方案2】:

作为临时更新,我的测试表明异步事件处理程序和小型同步等待都是合适的方法。通过仅将 Wait(.1) 委托给网络操作,启动 DispatcherInvoke(()=>{txtStatus+="WAIT.."} 并紧随其后的是 Dispatch(() =>{;/ /},DispatcherPriority.ApplicationIdle)。

在网络调用中使用异步事件处理程序和 IProgress(您甚至不需要 Dispatcher,只需更改绑定控件)会导致类似的进度更新,但初始响应可能不可接受。

使用同步等待会导致 UI Paint 停止,从而使任何逐渐更新的进度条显得非常生涩。

对我来说,进度条生涩不是问题,因为我通过 WAIT.. WAIT .. WAIT .. 向操作员提供直接反馈,而所有进度条所做的只是通过停止显示如何备份问题是,但我保留了足够的同步控制来干扰那里的振荡 CAUTION。振荡可能很不稳定,但训练有素的操作员会知道,如果 CAUTION 一直在切换,无论您处于多么不均匀的状态,即使渐变进度条停止并开始。

而且,我可以在同步代码中检测到这一点,然后即时对其进行处理,例如,卸载整个网络调用,然后强制用户排队和轮询。

更新

async Task<List<Double>> NetworkList(IProgress<Int32> NetworkProgress)
{
List<Double> _results = new List<Double>();
foreach(var aNetwork in LotsOfNetworksCouldTakeAwhile)
{
SendPingFarAwayLand();
await Task.Delay(delay: TimeSpan.Frommilliseconds(100);
Double PingTime = GetPingTimeOrNull();
_results.Add(PingTIme);
NetworkProgress.Report( aNetwork.NetworkID );
}
}


async btnClick
{
TimeSpan IDLE_LONG_ENOUGH_SO_DISPATCHER_UPDATES_UI = TimeSpan.FromMilliseconds(50);
txtStatus += "WORKING...";
await Task.Delay(delay: IDLE_LONG_ENOUGH_SO_DISPATCHER_UPDATES_UI);
Task<List<Double>> results = await NetworkList(_Progress);
dependencyObject1.ObservableList1.Add(results);
return;
}

这似乎至少在我的电脑上有效。大约 50 毫秒足以保证调度员立即处理您最新的“WORKING...”消息。如果你不这样做,你可能会在一个向上的循环中触发调度程序,但也可能不是,它会在 LongNetworkList 上启动,并且可能需要半秒才能看到正在发生的事情。

这几乎就像你希望冲刺队队长按秒表,还是和你一起跑步。如果您尝试 ping 两个低于 10 毫秒的网络,则延迟 50 毫秒甚至开始工作似乎都很多。但是,如果你有一个异常的,它会立即占用整个 100 毫秒的等待时间,并且仍然没有返回任何内容让你报告进度,那么在屏幕上放一些东西真是太好了。

感谢 Cleary,午餐后我马上切换到 Reactive.NET。

【讨论】:

  • 对此有什么想法吗?异步按钮处理程序中的文本框更新后等待 50 毫秒有什么魔力?那是什么规格?
  • 查看 Stephen Cleary 的知识渊博和精辟的投赞成票。我最后的考虑是,对 Dispatcher 执行 IDLE 任务,就像试图改变垃圾收集器的内务管理一样,可能对整体性能非常不利,迫使它重新评估数百条在另外 800 毫秒内到期的 Paint 消息。
猜你喜欢
  • 1970-01-01
  • 2021-09-04
  • 1970-01-01
  • 1970-01-01
  • 2021-06-30
  • 2013-04-15
  • 1970-01-01
  • 2023-03-23
  • 2021-06-16
相关资源
最近更新 更多