这是一个WinForms应用程序,其中有一个类,它有几个方法需要花费很多时间,这些方法以一定的时间间隔引发事件,我使用这些事件来更新GUI,更改标签,文本,颜色等。
任务运行在不同的线程上,所以它们不会阻塞UI,这很好,因为我要么从System.Timers.Timer
调用它们,要么创建一个任务列表并异步运行它们。
我遇到的问题是,在我为那个类创建的报告我不同状态的事件处理程序上,由于跨线程操作,我无法更新GUI,因此我所做的解决方法是检查EventHandler
上的InvokeRequired
,然后检查BeginInvoke
。
这样做的技巧,但我肯定这是不正确的,我的EventHandler可以调用数百次,每次我开始一个新的Invoke
。最好的方法是什么?我没有经验与代表,不知何故,我认为我应该使用他们,不知道如何
我在这里留下一段示例代码,它大大简化了工作负载和问题
public partial class Form1 : Form {
private Operational o = new();
private System.Timers.Timer WorkerTimer=new() { Interval=1000,Enabled=false};
public Form1() {
InitializeComponent();
o.OperationComplete += OperationCompleteEventHandler;
WorkerTimer.Elapsed += MonitorExecutor;
}
private async void MonitorExecutor(object? sender, ElapsedEventArgs e) {
List<Task> myTasks = new();
myTasks.Add(Task.Run(o.DoWork));
myTasks.Add(Task.Run(o.DoWork));
await Task.WhenAll(myTasks);
// Report all tasks have completed
}
private void OperationCompleteEventHandler(object? sender, EventArgs e) {
if (InvokeRequired) {
// Otherwise it will throw a cross-thread exception
Debug.WriteLine("Invoked Required!");
BeginInvoke(() => OperationCompleteEventHandler(sender, e));
return;
}
label1.Text = "WorkCompleted";
// But this could also take a lot of time,
// so I don't want this method to hang my thread
Thread.Sleep(500);
}
private void button1_Click(object sender, EventArgs e) {
WorkerTimer.Enabled = !WorkerTimer.Enabled;
button1.Text = WorkerTimer.Enabled ? "Running" : "Stopped";
}
}
public class Operational {
public event EventHandler? OperationComplete;
public void DoWork() {
// Long Operations
Thread.Sleep(500);
OperationComplete?.Invoke(this, new());
}
}
3条答案
按热度按时间vuv7lop31#
你没有正确使用
await
。假设WinForms设置了一个SynchronizationContext
,那么您应该依靠它来将延续(await
之后的位)封送到正确的线程上。有许多不同的方法可以实现这一点,但主要需要将
OperationComplete
的调用移动到普通的await
,并使用Task.Run
运行其余代码。然后您可以删除整个
if (InvokeRequired) {
块,因为现在OperationCompleteEventHandler
将始终在UI线程上运行。而
MonitorExecutor
可以简单地如果不想更改
DoWork
,则需要使用某种类型的事件到任务转换,例如A reusable pattern to convert event into task或General purpose FromEvent methodsnvhrwxg2#
我建议不要使用
System.Timers.Timer
组件。这不是一个设计良好的组件IMHO。你可以在这个答案的底部阅读我的论点。要定期执行工作而不存在重叠执行的风险,可以使用
async
方法和PeriodicTimer
组件。有关使用示例,请参阅此答案。至于每秒调用UI线程数百次,这不是一个好主意,因为UI线程负担过重的风险,使您的应用程序无响应,特别是如果它运行在慢速机器上。您应该将更新UI的频率降低到每秒最多10 - 20次更新。比这更频繁地更新UI是没有意义的,因为这超出了人类处理变化信息的视觉能力。为了限制信息流,您可以使用带有
Debounce
/Throttle
运算符的Rx等工具,或者使用使用Stopwatch
es和TimeSpan
s的自定义代码来实现相同的效果,或者使用递增的计数器并仅在++counter % 10 == 0
等时更新UI。q35jwt9p3#
如果您愿意使用其他方法来做到这一点,这里有一个使用Microsoft的Reactive Framework(又名Rx)的选项- NuGet
System.Reactive.Windows.Forms
并添加using System.Reactive.Linq;
-然后您可以这样做:它可能更简洁一点,可能更容易推理。