我在更新UI线程时遇到问题。应用程序正在为每个表单运行1个UI线程,这意味着仅将SyncronizationContext与UI线程一起使用不起作用。我这样做是为了循环更新性能以及模式弹出可能性,如在您可以使用表单之前选择值。
如何在ApplicationContext中创建它:
public AppContext()
{
foreach(var form in activeForms)
{
form.Load += Form_Load;
form.FormClosed += Form_FormClosed;
StartFormInSeparateThread(form);
//form.Show();
}
}
private void StartFormInSeparateThread(Form form)
{
Thread thread = new Thread(() =>
{
Application.Run(form);
});
thread.ApartmentState = ApartmentState.STA;
thread.Start();
}
每个for上都有控件,这些控件是数据绑定的,并且使用来自同一个数据绑定对象的值进行更新。控件是Labels和DataGridview(绑定到绑定列表)。理想的情况是使绑定列表线程安全,并在这些多个UI线程上执行。找到了一些我尝试过的示例,如下所示:
List<SynchronizationContext> listctx = new();
public ThreadSafeBindingList2()
{
//SynchronizationContext ctx = SynchronizationContext.Current;
//listctx.Add(ctx);
}
public void SyncContxt()
{
SynchronizationContext ctx = SynchronizationContext.Current;
listctx.Add(ctx);
}
protected override void OnAddingNew(AddingNewEventArgs e)
{
for (int i = 0; i < listctx.Count; i++)
{
if (listctx[i] == null)
{
BaseAddingNew(e);
}
else
{
listctx[i].Send(delegate
{
BaseAddingNew(e);
}, null);
}
}
}
void BaseAddingNew(AddingNewEventArgs e)
{
base.OnAddingNew(e);
}
protected override void OnListChanged(ListChangedEventArgs e)
{
for (int i = 0; i < listctx.Count; i++)
{
if (listctx[i] == null)
{
BaseListChanged(e);
}
else
{
listctx[i].Send(delegate
{
BaseListChanged(e);
}, null);
}
}
}
void BaseListChanged(ListChangedEventArgs e)
{
base.OnListChanged(e);
}
我还使用一个静态类作为所有控件的数据属性更改中心,这样我就不会多次更改数据绑定源(同样是出于性能考虑),其中我有一个后台工作者根据系统负载每1 - 3秒"滴答"一次:
private static void BackgroundWorker_DoWork(object? sender, DoWorkEventArgs e)
{
if (timerStart is false)
{
Thread.Sleep(6000);
timerStart = true;
}
while (DisplayTimerUpdateBGW.CancellationPending is false)
{
//UIThread.Post((object stat) => //Send
//{
threadSleepTimer = OrderList.Where(x => x.Status != OrderOrderlineStatus.Claimed).ToList().Count > 20 ? 2000 : 1000;
if (OrderList.Count > 40)
threadSleepTimer = 3000;
UpdateDisplayTimer();
//}, null);
Thread.Sleep(threadSleepTimer);
}
}
private static void UpdateDisplayTimer()
{
var displayLoopStartTimer = DateTime.Now;
TimeSpan displayLoopEndTimer = new();
Span<int> orderID = CollectionsMarshal.AsSpan(OrderList.Select(x => x.ID).ToList());
for (int i = 0; i < orderID.Length; i++)
{
OrderModel order = OrderList[i];
order.OrderInfo = "Ble";
Span<int> OrderLineID = CollectionsMarshal.AsSpan(order.Orderlines.Select(x => x.Id).ToList());
for (int j = 0; j < OrderLineID.Length; j++)
{
OrderlineModel ol = order.Orderlines[j];
TimeSpan TotalElapsedTime = ol.OrderlineCompletedTimeStamp != null ? (TimeSpan)(ol.OrderlineCompletedTimeStamp - ol.OrderlineReceivedTimeStamp) : DateTime.Now - ol.OrderlineReceivedTimeStamp;
string displaytimerValue = "";
if (ol.OrderlineCompletedTimeStamp == null)
displaytimerValue = TotalElapsedTime.ToString(@"mm\:ss");
else
displaytimerValue = $" {(DateTime.Now - ol.OrderlineCompletedTimeStamp)?.ToString(@"mm\:ss")} \n({TotalElapsedTime.ToString(@"mm\:ss")})";
ol.DisplayTimer = displaytimerValue;
}
}
}
理想情况下,我希望拥有标签和datagridview属性数据绑定,这样我就可以让INotifyPropertyChanged在所有UI线程中更新这些相关属性。
任何帮助将不胜感激!
1条答案
按热度按时间xvw2m8pv1#
了解这一点的许多方法之一是只有一个显示区域(尽管它可能由许多屏幕组成),并且在任何给定时刻只有一个元素可以更改。以我的想法,这意味着拥有多个UI线程往往会弄巧成拙(除非你的UI正在测试另一个UI)。而且由于机器的核心数量有限,拥有大量的线程(无论是UI线程还是辅助线程)意味着当线程关闭时,您将开始拥有大量的处理上下文的开销。
如果我们想创建一个Minimal Reproducible Example,其中有10个
Form
对象并行执行连续的“模拟更新”任务,我们可以做的不是使用您提到的“数据属性更改中心”,而是在那些表单类中实现INotifyPropertyChanged
,并使用静态PropertyChanged
事件,当更新发生时,该事件将被触发。主窗体订阅PropertyChanged
事件,并通过标识发送方和检查e
以确定哪个属性已更改,将新的Record
添加到BindingList<Record>
。在这种情况下,如果属性为TimeStamp
,则将接收到的数据封送到唯一的UI线程上,以在DataGridView
中显示结果。主窗体上的
DataGridView
使用此自定义类:“other”10表单使用此类模拟绑定源,如下所示:
我相信你的问题没有“正确”的答案,但我希望这里有一些东西,可能会推动你的事情向前发展。