c# - 閉じる - vb.net invoke




別のスレッドからGUIを更新するにはどうすればよいですか? (20)

長い仕事を扱う

.NET 4.5およびC#5.0以来、 asyncとともにタスクベースの非同期パターン(TAP)を使用する必要があります - すべての領域 (GUIを含む)でキーワードをasyncます:

TAPは、新しい開発に推奨される非同期設計パターンです

非同期プログラミングモデル(APM)イベントベースの非同期パターン(EAP) (後者はBackgroundWorkerクラスを含む)の代わりに使用できます。

次に、新しい開発の推奨ソリューションは次のとおりです。

  1. イベントハンドラの非同期実装(はい、それだけです):

    private async void Button_Clicked(object sender, EventArgs e)
    {
        var progress = new Progress<string>(s => label.Text = s);
        await Task.Factory.StartNew(() => SecondThreadConcern.LongWork(progress),
                                    TaskCreationOptions.LongRunning);
        label.Text = "completed";
    }
    
  2. UIスレッドに通知する第2のスレッドの実装:

    class SecondThreadConcern
    {
        public static void LongWork(IProgress<string> progress)
        {
            // Perform a long running work...
            for (var i = 0; i < 10; i++)
            {
                Task.Delay(500).Wait();
                progress.Report(i.ToString());
            }
        }
    }
    

次の点に注意してください。

  1. コールバックと明示的なスレッドを使わずに、短くクリーンなコードをシーケンシャルな方法で記述します。
  2. Thread代わりにTask
  3. asyncキーワードを使用すると、 awaitが使用され、イベントハンドラが終了するまでイベントハンドラが完了状態に達するのを防ぎ、その間にUIスレッドをブロックしません。
  4. Separation of Concerns(SoC)設計の原則をサポートし、明示的なディスパッチャと呼び出しを必要としないProgressクラス( IProgressインターフェイスを参照)。 作成場所(ここではUIスレッド)から現在のSynchronizationContextを使用します。
  5. TaskCreationOptions.LongRunningは、タスクをThreadPoolキューイングしないことを示します。

より詳細な例は、C#の将来: Joseph Albahariが 待っている人には良いことがあります。

UIスレッディングモデルの概念も参照してください。

例外の処理

以下のスニペットは、バックグラウンドでの実行中に複数のクリックが発生しないように、例外を処理し、ボタンのEnabledプロパティを切り替える方法の例です。

private async void Button_Click(object sender, EventArgs e)
{
    button.Enabled = false;

    try
    {
        var progress = new Progress<string>(s => button.Text = s);
        await Task.Run(() => SecondThreadConcern.FailingWork(progress));
        button.Text = "Completed";
    }
    catch(Exception exception)
    {
        button.Text = "Failed: " + exception.Message;
    }

    button.Enabled = true;
}

class SecondThreadConcern
{
    public static void FailingWork(IProgress<string> progress)
    {
        progress.Report("I will fail in...");
        Task.Delay(500).Wait();

        for (var i = 0; i < 3; i++)
        {
            progress.Report((3 - i).ToString());
            Task.Delay(500).Wait();
        }

        throw new Exception("Oops...");
    }
}

別のスレッドからLabelを更新する最も簡単な方法は何ですか?

私は、スレッド1のFormを持って、 thread2私は別のスレッド(スレッドthread2 )を開始しています。 thread2がいくつかのファイルを処理している間、 thread2 Labelthread2の作業の現在の状態で更新したいと思います。

どうやってやるの?


.NET 2.0の場合、ここでは、私が書いた素晴らしいコードがあります。これは、あなたが望むものを正確に行い、 Control任意のプロパティに対して機能します。

private delegate void SetControlPropertyThreadSafeDelegate(
    Control control, 
    string propertyName, 
    object propertyValue);

public static void SetControlPropertyThreadSafe(
    Control control, 
    string propertyName, 
    object propertyValue)
{
  if (control.InvokeRequired)
  {
    control.Invoke(new SetControlPropertyThreadSafeDelegate               
    (SetControlPropertyThreadSafe), 
    new object[] { control, propertyName, propertyValue });
  }
  else
  {
    control.GetType().InvokeMember(
        propertyName, 
        BindingFlags.SetProperty, 
        null, 
        control, 
        new object[] { propertyValue });
  }
}

これを次のように呼んでください:

// thread-safe equivalent of
// myLabel.Text = status;
SetControlPropertyThreadSafe(myLabel, "Text", status);

.NET 3.0以上を使用している場合、上記のメソッドをControlクラスの拡張メソッドとして書き直すと、次の呼び出しを簡素化できます。

myLabel.SetPropertyThreadSafe("Text", status);

更新05/10/2010:

.NET 3.0では、次のコードを使用する必要があります。

private delegate void SetPropertyThreadSafeDelegate<TResult>(
    Control @this, 
    Expression<Func<TResult>> property, 
    TResult value);

public static void SetPropertyThreadSafe<TResult>(
    this Control @this, 
    Expression<Func<TResult>> property, 
    TResult value)
{
  var propertyInfo = (property.Body as MemberExpression).Member 
      as PropertyInfo;

  if (propertyInfo == null ||
      [email protected]().IsSubclassOf(propertyInfo.ReflectedType) ||
      @this.GetType().GetProperty(
          propertyInfo.Name, 
          propertyInfo.PropertyType) == null)
  {
    throw new ArgumentException("The lambda expression 'property' must reference a valid property on this Control.");
  }

  if (@this.InvokeRequired)
  {
      @this.Invoke(new SetPropertyThreadSafeDelegate<TResult> 
      (SetPropertyThreadSafe), 
      new object[] { @this, property, value });
  }
  else
  {
      @this.GetType().InvokeMember(
          propertyInfo.Name, 
          BindingFlags.SetProperty, 
          null, 
          @this, 
          new object[] { value });
  }
}

これはLINQとラムダ式を使用して、よりクリーンでシンプルで安全な構文を可能にします。

myLabel.SetPropertyThreadSafe(() => myLabel.Text, status); // status has to be a string or this will fail to compile

プロパティ名はコンパイル時にチェックされるだけでなく、プロパティの型も同様です。したがって、(たとえば)ブール値プロパティに文字列値を代入することは不可能であり、実行時例外が発生します。

残念ながら、これは他のControlのプロパティと値を渡すなど、誰かが愚かなことをやめないようにするため、次のようにうまくコンパイルします。

myLabel.SetPropertyThreadSafe(() => aForm.ShowIcon, false);

そこで、渡されたプロパティがメソッドが呼び出されているControlに実際に属していることを確認するためにランタイムチェックを追加しました。 完璧ではありませんが、.NET 2.0よりはるかに優れています。

コンパイル時の安全性のためにこのコードを改善する方法について他に誰かが提案したら、コメントしてください!


Salvete! この質問を検索した結果FrankGOregon Ghostの回答が私にとって最も簡単なものであることがわかりました。 さて、私はVisual Basicでコーディングし、このスニペットをコンバーターで実行しました。 だから私はそれがどのように判明したのかよくわからない。

私はform_Diagnostics,と呼ばれるダイアログフォームを持っています。これはupdateDiagWindow,というリッチテキストボックスを持っています。これは、一種のログ表示として使用しています。 私はすべてのスレッドからテキストを更新できるようにする必要がありました。 余分な行によって、ウィンドウは自動的に最新の行にスクロールできます。

そして、プログラム全体のどこからでも1行で表示を更新できるようになりました。これは、スレッディングなしでも動作すると思います。

  form_Diagnostics.updateDiagWindow(whatmessage);

メインコード(これをフォームのクラスコードの中に入れます):

#region "---------Update Diag Window Text------------------------------------"
// This sub allows the diag window to be updated by all threads
public void updateDiagWindow(string whatmessage)
{
    var _with1 = diagwindow;
    if (_with1.InvokeRequired) {
        _with1.Invoke(new UpdateDiagDelegate(UpdateDiag), whatmessage);
    } else {
        UpdateDiag(whatmessage);
    }
}
// This next line makes the private UpdateDiagWindow available to all threads
private delegate void UpdateDiagDelegate(string whatmessage);
private void UpdateDiag(string whatmessage)
{
    var _with2 = diagwindow;
    _with2.appendtext(whatmessage);
    _with2.SelectionStart = _with2.Text.Length;
    _with2.ScrollToCaret();
}
#endregion


あなたはinvokeとdelegateを使う必要があります

private delegate void MyLabelDelegate();
label1.Invoke( new MyLabelDelegate(){ label1.Text += 1; });

これはIan KempのソリューションのC#3.0のバリエーションです:

public static void SetPropertyInGuiThread<C,V>(this C control, Expression<Func<C, V>> property, V value) where C : Control
{
    var memberExpression = property.Body as MemberExpression;
    if (memberExpression == null)
        throw new ArgumentException("The 'property' expression must specify a property on the control.");

    var propertyInfo = memberExpression.Member as PropertyInfo;
    if (propertyInfo == null)
        throw new ArgumentException("The 'property' expression must specify a property on the control.");

    if (control.InvokeRequired)
        control.Invoke(
            (Action<C, Expression<Func<C, V>>, V>)SetPropertyInGuiThread,
            new object[] { control, property, value }
        );
    else
        propertyInfo.SetValue(control, value, null);
}

あなたはこれを次のように呼んでいます:

myButton.SetPropertyInGuiThread(b => b.Text, "Click Me!")
  1. これは、 "as MemberExpression"の結果にヌルチェックを追加します。
  2. これは、静的型安全性を向上させます。

さもなければ、オリジナルはとても良い解決策です。


これはこれを行うべき古典的な方法です:

using System;
using System.Windows.Forms;
using System.Threading;

namespace Test
{
    public partial class UIThread : Form
    {
        Worker worker;

        Thread workerThread;

        public UIThread()
        {
            InitializeComponent();

            worker = new Worker();
            worker.ProgressChanged += new EventHandler<ProgressChangedArgs>(OnWorkerProgressChanged);
            workerThread = new Thread(new ThreadStart(worker.StartWork));
            workerThread.Start();
        }

        private void OnWorkerProgressChanged(object sender, ProgressChangedArgs e)
        {
            // Cross thread - so you don't get the cross-threading exception
            if (this.InvokeRequired)
            {
                this.BeginInvoke((MethodInvoker)delegate
                {
                    OnWorkerProgressChanged(sender, e);
                });
                return;
            }

            // Change control
            this.label1.Text = e.Progress;
        }
    }

    public class Worker
    {
        public event EventHandler<ProgressChangedArgs> ProgressChanged;

        protected void OnProgressChanged(ProgressChangedArgs e)
        {
            if(ProgressChanged!=null)
            {
                ProgressChanged(this,e);
            }
        }

        public void StartWork()
        {
            Thread.Sleep(100);
            OnProgressChanged(new ProgressChangedArgs("Progress Changed"));
            Thread.Sleep(100);
        }
    }


    public class ProgressChangedArgs : EventArgs
    {
        public string Progress {get;private set;}
        public ProgressChangedArgs(string progress)
        {
            Progress = progress;
        }
    }
}

ワーカースレッドにイベントがあります。 あなたのUIスレッドは、作業を行うために別のスレッドを開始し、ワーカースレッドの状態を表示できるようにそのワーカーイベントをフックアップします。

次にUIで、実際のコントロールを変更するためにスレッドを交差させる必要があります。ラベルやプログレスバーのように。


これを使用してラベルをリフレッシュしてみてください

public static class ExtensionMethods
{
    private static Action EmptyDelegate = delegate() { };

    public static void Refresh(this UIElement uiElement)
    {
        uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
    }
}

クラス変数を作成します。

SynchronizationContext _context;

UIを作成するコンストラクタで設定します。

var _context = SynchronizationContext.Current;

ラベルを更新する場合は、次のようにします。

_context.Send(status =>{
    // UPDATE LABEL
}, null);

シナリオのささいさのために、私は実際にステータスのUIスレッドのポーリングを持っています。 私はあなたがそれが非常にエレガントであることがわかるでしょう。

public class MyForm : Form
{
  private volatile string m_Text = "";
  private System.Timers.Timer m_Timer;

  private MyForm()
  {
    m_Timer = new System.Timers.Timer();
    m_Timer.SynchronizingObject = this;
    m_Timer.Interval = 1000;
    m_Timer.Elapsed += (s, a) => { MyProgressLabel.Text = m_Text; };
    m_Timer.Start();
    var thread = new Thread(WorkerThread);
    thread.Start();
  }

  private void WorkerThread()
  {
    while (...)
    {
      // Periodically publish progress information.
      m_Text = "Still working...";
    }
  }
}

ISynchronizeInvoke.BeginInvokeメソッドとISynchronizeInvoke.BeginInvokeメソッドを使用する場合に必要なマーシャリング操作が回避されます。 マーシャリングテクニックの使用には何も問題はありませんが、注意する必要があるいくつかの警告があります。

  • BeginInvoke頻繁に呼び出さないようにするか、メッセージポンプをオーバーランさせる可能性があります。
  • ワーカースレッドで呼び出しを呼び出すことはブロッキング呼び出しです。 そのスレッドで実行中の作業を一時的に停止します。

私がこの答えで提案する戦略は、スレッドの通信役割を逆転させます。 ワーカースレッドがデータをプッシュするのではなく、UIスレッドがデータをポーリングします。 これは、多くのシナリオで使用される一般的なパターンです。 ワーカースレッドから進捗情報を表示するだけで済むので、このソリューションはマーシャリングソリューションの優れた代替品であることがわかります。 それには以下の利点があります。

  • UIとワーカースレッドは、それらを密接に結合するControl.InvokeまたはControl.BeginInvokeアプローチとは対照的に、緩やかに結合されたままです。
  • UIスレッドは、ワーカースレッドの進行を妨げません。
  • ワーカースレッドは、UIスレッドが更新を費やしている時間を支配することはできません。
  • UIおよびワーカースレッドが操作を実行する間隔は、独立したままにすることができます。
  • ワーカースレッドは、UIスレッドのメッセージポンプをオーバーランすることはできません。
  • UIスレッドは、UIが更新されるタイミングと頻度を指示します。

ファイアアンドエクステンションメソッドfor .NET 3.5+

using System;
using System.Windows.Forms;

public static class ControlExtensions
{
    /// <summary>
    /// Executes the Action asynchronously on the UI thread, does not block execution on the calling thread.
    /// </summary>
    /// <param name="control"></param>
    /// <param name="code"></param>
    public static void UIThread(this Control @this, Action code)
    {
        if (@this.InvokeRequired)
        {
            @this.BeginInvoke(code);
        }
        else
        {
            code.Invoke();
        }
    }
}

これは、次のコード行を使用して呼び出すことができます。

this.UIThread(() => this.myLabel.Text = "Text Goes Here");

以前の回答のInvoke項目は必要ありません。

WindowsFormsSynchronizationContextを見る必要があります:

// In the main thread
WindowsFormsSynchronizationContext mUiContext = new WindowsFormsSynchronizationContext();

...

// In some non-UI Thread

// Causes an update in the GUI thread.
mUiContext.Post(UpdateGUI, userData);

...

void UpdateGUI(object userData)
{
    // Update your GUI controls here
}

多くの目的のために、これは次のように簡単です:

public delegate void serviceGUIDelegate();
private void updateGUI()
{
  this.Invoke(new serviceGUIDelegate(serviceGUI));
}

"serviceGUI()"は、フォーム内のGUIレベルのメソッド(これ)で、必要なだけ多くのコントロールを変更できます。 他のスレッドから "updateGUI()"を呼び出します。 値を渡すためにパラメータを追加することもできますし、不安定になる可能性のあるスレッドにアクセスするスレッド間に衝突する可能性がある場合は、必要に応じてクラススコープ変数をロックして使用することもできます。 GUI以外のスレッドがタイムクリティカルな場合(Brian Gideonの警告に留意してください)、Invokeの代わりにBeginInvokeを使用してください。


大多数の回答は、 発生するのを待っている競合状態であるControl.Invokeを使用します。 たとえば、受け入れられた答えを考えてみましょう。

string newText = "abc"; // running on worker thread
this.Invoke((MethodInvoker)delegate { 
    someLabel.Text = newText; // runs on UI thread
});

ユーザーがthis.Invokeが呼び出される直前にフォームを閉じると( thisFormオブジェクトです)、 ObjectDisposedExceptionが発生する可能性があります。

解決策は、 SynchronizationContext 、特にSynchronizationContext.Currentをhamilton.danielb示唆するように使用することです(他の回答は、完全に不要な特定のSynchronizationContext実装に依存します)。 私はSynchronizationContext.Postではなく、 SynchronizationContext.Sendを使用するようにコードを少し修正します(通常、ワーカースレッドが待機する必要はありません)。

public partial class MyForm : Form
{
    private readonly SynchronizationContext _context;
    public MyForm()
    {
        _context = SynchronizationContext.Current
        ...
    }

    private MethodOnOtherThread()
    {
         ...
         _context.Post(status => someLabel.Text = newText,null);
    }
}

.NET 4.0以降では、実際に非同期操作のタスクを使用する必要があります。 同等のタスクベースのアプローチ( TaskScheduler.FromCurrentSynchronizationContextを使用)については、 n-san's答えを参照してください。

最後に、.NET 4.5以降では、 RyszardDżegan氏が示したようにProgress<T> (基本的にはSynchronizationContext.Currentをキャプチャしてキャプチャします)を使用することもできます。


私が同じ問題に遭遇したとき、私はGoogleから助けを求めましたが、単純な解決策を提供するよりも、 MethodInvoker例やblah blah blahの例を挙げるともっと混乱しました。 だから私はそれを私自身で解決することに決めました。 ここに私のソリューションです:

このような代理人を作る:

Public delegate void LabelDelegate(string s);

void Updatelabel(string text)
{
   if (label.InvokeRequired)
   {
       LabelDelegate LDEL = new LabelDelegate(Updatelabel);
       label.Invoke(LDEL, text);
   }
   else
       label.Text = text
}

このような新しいスレッドでこの関数を呼び出すことができます

Thread th = new Thread(() => Updatelabel("Hello World"));
th.start();

Thread(() => .....)と混同しないでください。私はスレッドで作業するときに無名関数またはラムダ式を使用します。コード行を減らすには、ThreadStart(..)ここで説明していない方法も使用できます。


私が考える最も簡単な方法:

   void Update()
   {
       BeginInvoke((Action)delegate()
       {
           //do your update
       });
   }

UIスレッドでは、同期コンテキストタスクスケジューラを要求することができます。UIスレッドのすべてをスケジュールするTaskSchedulerを提供します。

次に、結果を準備するときに別のタスク(UIスレッドでスケジュールされている)がそれを選択してラベルに割り当てるように、タスクを連鎖させることができます。

public partial class MyForm : Form
{
  private readonly TaskScheduler _uiTaskScheduler;
  public MyForm()
  {
    InitializeComponent();
    _uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
  }

  private void buttonRunAsyncOperation_Click(object sender, EventArgs e)
  {
    RunAsyncOperation();
  }

  private void RunAsyncOperation()
  {
    var task = new Task<string>(LengthyComputation);
    task.ContinueWith(antecedent =>
                         UpdateResultLabel(antecedent.Result), _uiTaskScheduler);
    task.Start();
  }

  private string LengthyComputation()
  {
    Thread.Sleep(3000);
    return "47";
  }

  private void UpdateResultLabel(string text)
  {
    labelResult.Text = text;
  }
}

これは、並行コードを書くための好ましい方法であるタスク(スレッドではない)に対して機能します


シンプルなソリューションの中にはInvokeRequiredチェックを省略していることに気付いたので、警告を追加したいと思っていました。

は、コントロールのウィンドウハンドルが(フォームが表示される前などに)作成される前にコードが実行されるとInvoke、例外がスローされることに気付きました。だから私はいつものチェックをお勧めしますInvokeRequired呼び出す前InvokeBeginInvoke


私のバージョンは、再帰的な "mantra"の1行を挿入することです:

引数がない場合:

    void Aaaaaaa()
    {
        if (InvokeRequired) { Invoke(new Action(Aaaaaaa)); return; } //1 line of mantra

        // Your code!
    }

引数を持つ関数の場合:

    void Bbb(int x, string text)
    {
        if (InvokeRequired) { Invoke(new Action<int, string>(Bbb), new[] { x, text }); return; }
        // Your code!
    }

それはITです。

いくつかの議論:通常、コードの読みやすさは、if ()ステートメントの後に{} を1行に入れるのは悪いです。しかし、この場合、これはまったく同じ「マントラ」です。このメソッドがプロジェクト全体で一貫していると、コードの可読性が損なわれません。そして、コードを捨てることなく(5行ではなく1行のコードで)保存します。

ご覧のとおりif(InvokeRequired) {something long}、あなただけ知っている「この機能は、別のスレッドから呼び出すことが安全です」。


私はちょうど答えを読んで、これは非常にホットな話題に見えます。私は現在、.NET 3.5 SP1とWindows Formsを使用しています。

InvokeRequiredプロパティを使用する以前の回答でよく説明されているよく知られている式は、ほとんどの場合をカバーしますが、プール全体はカバーしません。

ハンドルがまだ作成されていない場合はどうなりますか?

InvokeRequiredプロパティ、説明したように、ここで(MSDNにControl.InvokeRequiredプロパティ参照)がコールが、偽GUIスレッドではないスレッドから作製した場合に真を返すのいずれかの場合、コールは、GUIスレッドから作られた、またはされた場合、ハンドルがありましたまだ作成されていません。

他のスレッドがモーダルフォームを表示して更新したい場合は、例外を見つけることができます。フォームをモーダルに表示したいので、次の操作を実行できます。

private MyForm _gui;

public void StartToDoThings()
{
    _gui = new MyForm();
    Thread thread = new Thread(SomeDelegate);
    thread.Start();
    _gui.ShowDialog();
}

デリゲートは、GUI上のラベルを更新することができます:

private void SomeDelegate()
{
    // Operations that can take a variable amount of time, even no time
    //... then you update the GUI
    if(_gui.InvokeRequired)
        _gui.Invoke((Action)delegate { _gui.Label1.Text = "Done!"; });
    else
        _gui.Label1.Text = "Done!";
}

これにより、GUIスレッドがフォームハンドルを作成するのにかかる時間よりも、ラベルの更新前の操作が「時間がかかりません」(読み込み、簡略化として解釈する)場合にInvalidOperationExceptionが発生する可能性があります。これは、ShowDialog()メソッド内で発生します。

次のようにHandleもチェックする必要があります:

private void SomeDelegate()
{
    // Operations that can take a variable amount of time, even no time
    //... then you update the GUI
    if(_gui.IsHandleCreated)  //  <---- ADDED
        if(_gui.InvokeRequired)
            _gui.Invoke((Action)delegate { _gui.Label1.Text = "Done!"; });
        else
            _gui.Label1.Text = "Done!";
}

ハンドルがまだ作成されていない場合は、実行する操作を処理できます.GUIの更新を無視するか(上のコードに示すように)、待つことができます(危険性が高い)。これは質問に答えるはずです。

オプションのもの:個人的に私は次のコードを書きました:

public class ThreadSafeGuiCommand
{
  private const int SLEEPING_STEP = 100;
  private readonly int _totalTimeout;
  private int _timeout;

  public ThreadSafeGuiCommand(int totalTimeout)
  {
    _totalTimeout = totalTimeout;
  }

  public void Execute(Form form, Action guiCommand)
  {
    _timeout = _totalTimeout;
    while (!form.IsHandleCreated)
    {
      if (_timeout <= 0) return;

      Thread.Sleep(SLEEPING_STEP);
      _timeout -= SLEEPING_STEP;
    }

    if (form.InvokeRequired)
      form.Invoke(guiCommand);
    else
      guiCommand();
  }
}

私は、このThreadSafeGuiCommandのインスタンスで別のスレッドによって更新されるフォームをフィードし、このように(私のフォームで)GUIを更新するメソッドを定義します:

public void SetLabeTextTo(string value)
{
  _threadSafeGuiCommand.Execute(this, delegate { Label1.Text = value; });
}

このようにして、任意のスレッドがコールを行い、必要に応じて十分に定義された時間(タイムアウト)を待っている間、GUIを更新するようにします。





user-interface