winforms 多UI线程和数据绑定问题

zzlelutf  于 2022-12-19  发布在  其他
关注(0)|答案(1)|浏览(162)

我在更新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线程中更新这些相关属性。
任何帮助将不胜感激!

xvw2m8pv

xvw2m8pv1#

了解这一点的许多方法之一是只有一个显示区域(尽管它可能由许多屏幕组成),并且在任何给定时刻只有一个元素可以更改。以我的想法,这意味着拥有多个UI线程往往会弄巧成拙(除非你的UI正在测试另一个UI)。而且由于机器的核心数量有限,拥有大量的线程(无论是UI线程还是辅助线程)意味着当线程关闭时,您将开始拥有大量的处理上下文的开销。
如果我们想创建一个Minimal Reproducible Example,其中有10个Form对象并行执行连续的“模拟更新”任务,我们可以做的不是使用您提到的“数据属性更改中心”,而是在那些表单类中实现INotifyPropertyChanged,并使用静态PropertyChanged事件,当更新发生时,该事件将被触发。主窗体订阅PropertyChanged事件,并通过标识发送方和检查e以确定哪个属性已更改,将新的Record添加到BindingList<Record>。在这种情况下,如果属性为TimeStamp,则将接收到的数据封送到唯一的UI线程上,以在DataGridView中显示结果。

public partial class MainForm : Form
{
    public MainForm() => InitializeComponent();
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        // Subscribe to the static event here.
        FormWithLongRunningTask.PropertyChanged += onAnyFWLRTPropertyChanged;
        // Start up the 10 forms which will do "popcorn" updates.
        for (int i = 0; i < 10; i++)
        {
            new FormWithLongRunningTask { Name = $"Form{i}" }.Show(this);
        }
    }
    private void onAnyFWLRTPropertyChanged(object? sender, PropertyChangedEventArgs e)
    {
        if (sender is FormWithLongRunningTask form)
        {
            BeginInvoke(() =>
            {
                switch (e.PropertyName)
                {
                    case nameof(FormWithLongRunningTask.TimeStamp):
                        dataGridViewEx.DataSource.Add(new Record
                        {
                            Sender = form.Name,
                            TimeStamp = form.TimeStamp,
                        });
                        break;
                    default:
                        break;
                }
            });
        }
    }
}

主窗体上的DataGridView使用此自定义类:

class DataGridViewEx : DataGridView
{
    public new BindingList<Record> DataSource { get; } = new BindingList<Record>();
    protected override void OnHandleCreated(EventArgs e)
    {
        base.OnHandleCreated(e);
        if (!DesignMode)
        {
            base.DataSource = this.DataSource;
            AllowUserToAddRows = false;

            #region F O R M A T    C O L U M N S
            DataSource.Add(new Record());
            Columns[nameof(Record.Sender)].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
            var col = Columns[nameof(Record.TimeStamp)];
            col.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
            col.DefaultCellStyle.Format = "hh:mm:ss tt";
            DataSource.Clear();
            #endregion F O R M A T    C O L U M N S
        }
    }
    protected override void OnCellPainting(DataGridViewCellPaintingEventArgs e)
    {
        base.OnCellPainting(e);
        if ((e.RowIndex > -1) && (e.RowIndex < DataSource.Count))
        {
            var record = DataSource[e.RowIndex];
            var color = _colors[int.Parse(record.Sender.Replace("Form", string.Empty))];
            e.CellStyle.ForeColor = color;
            if (e.ColumnIndex > 0)
            {
                CurrentCell = this[0, e.RowIndex];
            }
        }
    }
    Color[] _colors = new Color[]
    {
        Color.Black, Color.Blue, Color.Green, Color.LightSalmon, Color.SeaGreen,
        Color.BlueViolet, Color.DarkCyan, Color.Maroon, Color.Chocolate, Color.DarkKhaki
    };
}    
class Record
{
    public string Sender { get; set; } = string.Empty;
    public DateTime TimeStamp { get; set; }
}

“other”10表单使用此类模拟绑定源,如下所示:

public partial class FormWithLongRunningTask : Form, INotifyPropertyChanged
{
    static Random _rando = new Random(8);
    public FormWithLongRunningTask() => InitializeComponent();

    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        _ = runRandomDelayLoop();
    }
    private async Task runRandomDelayLoop()
    {
        while(true)
        {
            try
            {
                await Task.Delay(TimeSpan.FromSeconds(_rando.NextDouble() * 10));
                TimeStamp = DateTime.Now;
                Text = $"@ {TimeStamp.ToLongTimeString()}";
                BringToFront();
            }
            catch (ObjectDisposedException)
            {
            }
        }
    }
    DateTime _timeStamp = DateTime.Now;
    public DateTime TimeStamp
    {
        get => _timeStamp;
        set
        {
            if (!Equals(_timeStamp, value))
            {
                _timeStamp = value;
                OnPropertyChanged();
            }
        }
    }
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    event PropertyChangedEventHandler? INotifyPropertyChanged.PropertyChanged
    {
        add => PropertyChanged += value;
        remove => PropertyChanged -= value;
    }
    public static event PropertyChangedEventHandler? PropertyChanged;
}

我相信你的问题没有“正确”的答案,但我希望这里有一些东西,可能会推动你的事情向前发展。

相关问题