簡體   English   中英

清理使用InvokeRequired亂丟的代碼

[英]Cleaning up code littered with InvokeRequired

我知道當從任何非UI線程操作UI控件時,您必須封送對UI線程的調用以避免問題。 一般的共識是您應該使用測試InvokeRequired,如果為true,則使用.Invoke來執行封送處理。

這會導致很多代碼看起來像這樣:

private void UpdateSummary(string text)
{
    if (this.InvokeRequired)
    {
        this.Invoke(new Action(() => UpdateSummary(text)));
    }
    else
    {
        summary.Text = text;
    }
}

我的問題是:我可以省略InvokeRequired測試並只調用Invoke,如下所示:

private void UpdateSummary(string text)
{
    this.Invoke(new Action(() => summary.Text = text));
}

這樣做有問題嗎? 如果是這樣,是否有更好的方法來保持InvokeRequired測試,而不必在整個地方復制和粘貼此模式?

那怎么樣:

public static class ControlHelpers
{
    public static void InvokeIfRequired<T>(this T control, Action<T> action) where T : ISynchronizeInvoke
    {
        if (control.InvokeRequired)
        {
            control.Invoke(new Action(() => action(control)), null);
        }
        else
        {
            action(control);
        }
    }
}

像這樣使用它:

private void UpdateSummary(string text)
{
    summary.InvokeIfRequired(s => { s.Text = text });
}

從UI線程調用Invoke有點效率低下。

相反,您可以創建一個采用Action參數的InvokeIfNeeded擴展方法。 (這也允許您從調用點刪除new Action(...)

我一直在閱讀關於添加邏輯檢查來反復討論的問題,以確定在不在UI線程上而不在UI線程本身上時是否應該使用IFF調用。 我寫了一個類來檢查執行(通過秒表 )各種方法的時間,以粗略估計一種方法相對於另一種方法的效率。

對於你們中的一些人來說結果可能會令人驚訝(這些測試是通過Form.Shown事件運行的):

     // notice that we are updating the form's title bar 10,000 times
     // directly on the UI thread
     TimedAction.Go
     (
        "Direct on UI Thread",
        () =>
        {
           for (int i = 0; i < 10000; i++)
           {
              this.Text = "1234567890";
           }
        }
     );

     // notice that we are invoking the update of the title bar
     // (UI thread -> [invoke] -> UI thread)
     TimedAction.Go
     (
        "Invoke on UI Thread",
        () =>
        {
           this.Invoke
           (
              new Action
              (
                 () =>
                 {
                    for (int i = 0; i < 10000; i++)
                    {
                       this.Text = "1234567890";
                    }
                 }
              )
           );
        }
     );

     // the following is invoking each UPDATE on the UI thread from the UI thread
     // (10,000 invokes)
     TimedAction.Go
     (
        "Separate Invoke on UI Thread",
        () =>
        {
           for (int i = 0; i < 10000; i++)
           {
              this.Invoke
              (
                 new Action
                 (
                    () =>
                    {
                       this.Text = "1234567890";
                    }
                 )
              );
           }
        }
     );

結果如下:

  • TimedAction :: Go()+ 0 - 調試:[DEBUG]秒表[在UI線程上直接]: 300ms
  • TimedAction :: Go()+ 0 - 調試:[DEBUG]秒表[在UI線程上調用]: 299ms
  • TimedAction :: Go()+ 0 - 調試:[DEBUG]秒表[在UI線程上單獨調用]: 649ms

我的結論是,您可以隨時安全地調用,無論您是在UI線程還是工作線程,都沒有通過消息泵循環回來的顯着開銷。 但是,在UI線程上執行大部分工作而不是多次調用UI線程(通過Invoke() )是有利的並且大大提高了效率。

我意識到已經有一個答案 ,但是我也想發布它(我也在這里發布)。

我的稍微不同,它可以稍微更安全地處理空控件,並在必要時返回結果。 當嘗試調用在父窗體上顯示可能為null的MessageBox並返回顯示MessageBox的DialogResult時,這兩個對我來說都派上了用場。


using System;
using System.Windows.Forms;

/// <summary>
/// Extension methods acting on Control objects.
/// </summary>
internal static class ControlExtensionMethods
{
    /// <summary>
    /// Invokes the given action on the given control's UI thread, if invocation is needed.
    /// </summary>
    /// <param name="control">Control on whose UI thread to possibly invoke.</param>
    /// <param name="action">Action to be invoked on the given control.</param>
    public static void MaybeInvoke(this Control control, Action action)
    {
        if (control != null && control.InvokeRequired)
        {
            control.Invoke(action);
        }
        else
        {
            action();
        }
    }

    /// <summary>
    /// Maybe Invoke a Func that returns a value.
    /// </summary>
    /// <typeparam name="T">Return type of func.</typeparam>
    /// <param name="control">Control on which to maybe invoke.</param>
    /// <param name="func">Function returning a value, to invoke.</param>
    /// <returns>The result of the call to func.</returns>
    public static T MaybeInvoke<T>(this Control control, Func<T> func)
    {
        if (control != null && control.InvokeRequired)
        {
            return (T)(control.Invoke(func));
        }
        else
        {
            return func();
        }
    }
}

用法:

myForm.MaybeInvoke(() => this.Text = "Hello world");

// Sometimes the control might be null, but that's okay.
var dialogResult = this.Parent.MaybeInvoke(() => MessageBox.Show(this, "Yes or no?", "Choice", MessageBoxButtons.YesNo));

我不相信Control.Invoke是更新UI的最佳選擇。 在你的情況下,我不能肯定地說,因為我不知道調用UpdateSummary的情況。 但是,如果您定期調用它作為顯示進度信息的機制(這是我從代碼片段中得到的印象),那么通常有更好的選擇。 該選項是讓UI線程輪詢狀態,而不是讓工作線程推送它。

在這種情況下應該考慮輪詢方法的原因是因為:

  • 它打破了Control.Invoke強加的UI和工作線程之間的緊密耦合。
  • 它將更新UI線程的責任放在它應該屬於的UI線程上。
  • UI線程可以決定更新的發生時間和頻率。
  • UI消息泵沒有溢出的風險,就像工作線程啟動的編組技術一樣。
  • 在繼續執行下一步之前,工作線程不必等待確認已執行更新(即,您在UI和工作線程上獲得更多吞吐量)。

因此,請考慮創建一個System.Windows.Forms.Timer ,它定期檢查要在Control上顯示的文本,而不是從工作線程啟動推送。 同樣,不知道您的具體要求,我不願意說這肯定你需要去的方向,但在 大多數 很多情況下, 不是更好Control.Invoke選項。

顯然,這種方法完全消除了InvokedRequired檢查的必要性。 沒關系,事實上它簡化了UI /工作線程交互的 所有 其他方面。

我對視圖控件的首選方法是將所有控件狀態封裝在一個類中,該類可以在不經歷任何不一致狀態的情況下進行更新(一種簡單的方法是將所有需要更新的內容放在一起一個不可變的類,並在需要更新時創建該類的新實例)。 然后有一個方法,它將Interlocked.Exchange一個updateNeeded標志,如果沒有更新掛起但IsHandleCreated為true,則BeginInvoke更新過程。 在進行任何更新之前,更新過程應該首先清除updateNeeded標志(如果有人在此時嘗試更新控件,則另一個請求將是BeginInvoked)。 請注意,如果控件在您准備更新時被釋放,則必須准備捕獲並吞下異常(我認為是IllegalOperation)。

順便說一句,如果一個控件尚未加入一個線程(通過添加到一個可見的窗口,或者它的窗口變得可見),直接更新它是合法的,但在它上面使用BeginInvoke或Invoke是不合法的。

我還無法發表評論,希望有人會看到這個並將其添加到已接受的答案中,否則就是現場。

control.Invoke(new Action(() => action(control))); 應該讀
control.Invoke(new Action(() => action(control)), null);

如上所述,接受的答案將無法編譯,因為ISynchronizeInvoke.Invoke()沒有像Control.Invoke()那樣只有1個參數的重載。

另一件事是使用可能更清楚
summary.InvokeIfRequired(c => { summary.Text = text; }); 而不是書面summary.InvokeIfRequired(c => { textBox.Text = text });

如果可能,使用BackgroudWorker更容易使UI響應並使用ReportProgress更新UI,因為它在與UI相同的線程上運行,因此您不需要InvokeRequired。

暫無
暫無

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

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