Winforms updates with high performance

前端 未结 2 1299
无人共我
无人共我 2020-12-12 03:33

Let me setup this question with some background information, we have a long running process which will be generating data in a Windows Form. So, obviously some form of multi

相关标签:
2条回答
  • 2020-12-12 04:15

    There is little point in trying to report progress any faster than the user can keep track of it.

    If your background thread is posting messages faster than the GUI can process them, (and you have all the symtoms of this - poor GUI resonse to user input, DoEvents runaway recursion), you have to throttle the progress updates somehow.

    A common approach is to update the GUI using a main-thread form timer at a rate sufficiently small that the user sees an acceptable progress readout. You may need a mutex or critical section to protect shared data, though that amy not be necessary if the progress value to be monitored is an int/uint.

    An alternative is to strangle the thread by forcing it to block on an event or semaphore until the GUI is idle.

    0 讨论(0)
  • 2020-12-12 04:27

    The UI thread should not be held for more than 50ms by a CPU-bound operation taking place on it ("The 50ms Rule"). Usually, the UI work items are executed upon events, triggered by user input, completion of an IO-bound operation or a CPU-bound operation offloaded to a background thread.

    However, there are some rare cases when the work needs to be done on the UI thread. For example, you may need to poll a UI control for changes, because the control doesn't expose proper onchange-style event. Particularly, this applies to WebBrowser control (DOM Mutation Observers are only being introduced, and IHTMLChangeSink doesn't always work reliably, in my experience).

    Here is how it can be done efficiently, without blocking the UI thread message queue. A few key things was used here to make this happen:

    • The UI work tasks yields (via Application.Idle) to process any pending messages
    • GetQueueStatus is used to decide on whether to yield or not
    • Task.Delay is used to throttle the loop, similar to a timer event. This step is optional, if the polling needs to be as precise as possible.
    • async/await provide pseudo-synchronous linear code flow.

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    
    namespace WinForms_21643584
    {
        public partial class MainForm : Form
        {
            EventHandler ContentChanged = delegate { };
    
            public MainForm()
            {
                InitializeComponent();
                this.Load += MainForm_Load;
            }
    
            // Update UI Task
            async Task DoUiWorkAsync(CancellationToken token)
            {
                try
                {
                    var startTick = Environment.TickCount;
                    var editorText = this.webBrowser.Document.Body.InnerText;
                    while (true)
                    {
                        // observe cancellation
                        token.ThrowIfCancellationRequested();
    
                        // throttle (optional)
                        await Task.Delay(50);
    
                        // yield to keep the UI responsive
                        await ApplicationExt.IdleYield();
    
                        // poll the content for changes
                        var newEditorText = this.webBrowser.Document.Body.InnerText;
                        if (newEditorText != editorText)
                        {
                            editorText = newEditorText;
                            this.status.Text = "Changed on " + (Environment.TickCount - startTick) + "ms";
                            this.ContentChanged(this, EventArgs.Empty);
                        }
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);
                }
            }
    
            async void MainForm_Load(object sender, EventArgs e)
            {
                // navigate the WebBrowser
                var documentTcs = new TaskCompletionSource<bool>();
                this.webBrowser.DocumentCompleted += (sIgnore, eIgnore) => documentTcs.TrySetResult(true);
                this.webBrowser.DocumentText = "<div style='width: 100%; height: 100%' contentEditable='true'></div>";
                await documentTcs.Task;
    
                // cancel updates in 10 s
                var cts = new CancellationTokenSource(20000);
    
                // start the UI update 
                var task = DoUiWorkAsync(cts.Token);
            }
        }
    
        // Yield via Application.Idle
        public static class ApplicationExt
        {
            public static Task<bool> IdleYield()
            {
                var idleTcs = new TaskCompletionSource<bool>();
                if (IsMessagePending())
                {
                    // register for Application.Idle
                    EventHandler handler = null;
                    handler = (s, e) =>
                    {
                        Application.Idle -= handler;
                        idleTcs.SetResult(true);
                    };
                    Application.Idle += handler;
                }
                else
                    idleTcs.SetResult(false);
                return idleTcs.Task;
            }
    
            public static bool IsMessagePending()
            {
                // The high-order word of the return value indicates the types of messages currently in the queue. 
                return 0 != (GetQueueStatus(QS_MASK) >> 16 & QS_MASK);
            }
    
            const uint QS_MASK = 0x1FF;
    
            [System.Runtime.InteropServices.DllImport("user32.dll")]
            static extern uint GetQueueStatus(uint flags);
        }
    }
    

    This code is specific to WinForms. Here is a similar approach for WPF.

    0 讨论(0)
提交回复
热议问题