簡體   English   中英

多 UI 線程和數據綁定問題

[英]Multi UI-threading and databinding issues

我在更新 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();
        
    }

每個控件都有數據綁定並使用來自同一數據綁定對象的值進行更新。 控件是標簽和 DataGridview(綁定到綁定列表)。 理想的是讓 Bindinglist 線程安全並在這些多個 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 線程中的這些相關屬性。

任何幫助,將不勝感激!

查看此內容的多種方法之一是只有一個顯示區域(盡管可能由許多屏幕組成)並且其中只有一個元素可以在任何給定時刻更改。 在我看來,這意味着擁有多個 UI 線程通常會弄巧成拙(除非您的 UI 正在測試另一個 UI)。 由於機器的內核數量有限,因此擁有大量線程(無論是 UI 線程還是 worker 線程)意味着當線程關閉時,您可能會開始有大量開銷來編組上下文。

如果我們想制作一個具有 10 個並行執行連續“模擬更新”任務的Form對象的最小可重現示例,我們可以做的而不是您提到的“數據屬性更改中心”是在那些具有靜態PropertyChanged的表單類中實現INotifyPropertyChanged發生更新時觸發的事件。 要在FormWithLongRunningTask是綁定源的情況下模擬數據綁定,主窗體訂閱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 的主窗體


主窗體上的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; }
}

“其他”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;
}

我相信你的問題沒有“正確”的答案,但我希望這里有一些東西可以為你推動事情向前發展。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM