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


1393

Label別のものから更新する最も簡単な方法はどれThreadですか?

  • 私はでForm実行してthread1おり、そこから別のスレッド(thread2)を開始しています。

  • thread2がいくつかのファイルを処理している間Labelに、の作業のForm現在のステータスでを更新したいと思いますthread2

どうすればできますか?


25
.net 2.0+には、このためのBackgroundWorkerクラスがありません。UIスレッド対応。1. BackgroundWorkerを作成します2. 2つのデリゲートを追加します(1つは処理用、もう1つは完了用)
Preet Sangha


4
.NET 4.5およびC#5.0の回答をご覧ください:stackoverflow.com/a/18033198/2042090
RyszardDżegan13年

5
この質問は、Gtk#GUIには適用されません。Gtk#については、これこの回答を参照してください。
hlovdal

注意してください:この質問に対する答えは、雑然としたOT(「ここに私のWPFアプリのためにやったこと」)と歴史的な.NET 2.0アーティファクトです。
マーク

回答:


768

.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);

2010年5月10日更新:

.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 ||
      !@this.GetType().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バージョンよりもはるかに優れています。

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


3
this.GetType()がpropertyInfo.ReflectedTypeと同じように評価される場合があります(WinFormsのLinkLabelなど)。大きなC#の経験はありませんが、例外の条件は次のようになるはずだと思います:if(propertyInfo == null ||(!@this.GetType()。IsSubclassOf(propertyInfo.ReflectedType)&& @ this.GetType( )!= propertyInfo.ReflectedType)|| @ this.GetType()。GetProperty(propertyInfo.Name、propertyInfo.PropertyType)== null)
Corvin

9
@lanは、これSetControlPropertyThreadSafe(myLabel, "Text", status)を別のモジュール、クラス、フォームから呼び出すことができます
Smith

71
提供されるソリューションは不必要に複雑です。シンプルさを重視する場合は、Marc GravellのソリューションまたはZaid Masudのソリューションを参照してください。
フランクHileman 14年

8
すべての呼び出しに多くのリソースがかかるため、複数のプロパティを更新する場合、このソリューションは大量のリソースを無駄にします。とにかく、スレッドセーフの機能が意図されていた方法ではないと思います。UIの更新アクションをカプセル化し、一度起動します(プロパティごとではありません)
コンソール

4
なぜこのコードをBackgroundWorkerコンポーネントで使用するのですか?
アンディ

1080

最も簡単な方法は、に渡される匿名メソッドですLabel.Invoke

// Running on the worker thread
string newText = "abc";
form.Label.Invoke((MethodInvoker)delegate {
    // Running on the UI thread
    form.Label.Text = newText;
});
// Back on the worker thread

Invoke完了するまで実行がブロックされることに注意してください。これは同期コードです。質問は非同期コードについては問いませんが、それについて知りたいときに非同期コードを書くことについて、Stack Overflowには多くのコンテンツがあります。


8
OPがフォーム以外のクラス/インスタンスについて言及していないので、それは悪いデフォルトではありません...
Marc Gravell

39
「this」キーワードが「Control」クラスを参照していることを忘れないでください。
AZ。

8
@codecompletingはどちらの方法でも安全であり、すでにワーカーを使用していることがわかっているので、なぜ私たちが知っていることを確認するのでしょうか?
Marc Gravell

4
@Dragoufは実際にはそうではありません-このメソッドを使用するポイントの1つは、ワーカーで実行される部分とUIスレッドで実行される部分がすでにわかっていることです。確認する必要はありません。
マルクグラベル

3
@ Joan.bdm私がそれにコメントするのに十分な前後関係はどこにもありません
マークグラベル

401

長い作業の取り扱い

以来、.NET 4.5とC#5.0あなたが使用する必要があるタスクベースの非同期パターン(TAP)と一緒に非同期 - のawaitキーワードをすべての分野で(GUIを含みます):

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. 関心の分離(SoC)設計原則をサポートし、明示的なディスパッチャーと呼び出しを必要としないProgressクラス(IProgressインターフェイスを参照)。作成場所(ここではUIスレッド)から現在のSynchronizationContextを使用します。
  5. TaskCreationOptions.LongRunningは、タスクをThreadPoolにキューしないことを示唆します。

より詳細な例については、C#の未来を参照してください。ジョセフアルバハリによって「待っている」人には良いことが起こります。

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...");
    }
}

2
SecondThreadConcern.LongWork()例外をスローした場合、UIスレッドでキャッチできますか?これは素晴らしい投稿です。
kdbanman 2015

2
回答にセクションを追加して、要件を満たします。よろしく。
RyszardDżegan15年

3
ExceptionDispatchInfoクラスは、非同期のawaitパターンでUIスレッド上でバックグラウンド例外を再スローの奇跡を担当しています。
RyszardDżegan15年

1
これを行うこの方法は、単にInvoke / Beginを呼び出すよりもはるかに冗長であると考えるのは私だけですか?
MeTitus

2
Task.Delay(500).Wait()?現在のスレッドをブロックするだけのタスクを作成する意味は何ですか?スレッドプールスレッドをブロックしないでください。
Yarik

236

.NET 4用のMarc Gravellの最も簡単なソリューションのバリエーション:

control.Invoke((MethodInvoker) (() => control.Text = "new text"));

または、代わりにアクションデリゲートを使用します。

control.Invoke(new Action(() => control.Text = "new text"));

2つの比較については、ここを参照してください:MethodInvokerとAction for Control.BeginInvoke


1
この例の「コントロール」とは何ですか?私のUIコントロール?これをラベルコントロールのWPFに実装しようとしていますが、Invokeは私のラベルのメンバーではありません。
Dbloom 2017

拡張メソッド @styxriverのよう stackoverflow.com/a/3588137/206730
Kiquenet

「アクションy;」を宣言する クラスまたはメソッド内でtextプロパティを変更し、次のコードでテキストを更新します 'yourcontrol.Invoke(y =()=> yourcontrol.Text = "new text");'
アントニオレイテ2017

4
@DbloomはWinForms専用であるため、メンバーではありません。WPFのためには、Dispatcher.Invoke使用
SLW

1
私はこの解決策に従っていましたが、UIが更新されない場合がありました。this.refresh()GUIを強制的に無効にして再描画する必要があることがわかりました..それが役に立った場合は..
Rakibul Haq

137

.NET 3.5以降用のFire and Forget拡張メソッド

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");

5
@this使用のポイントは何ですか?「コントロール」は同等ではないでしょうか?@thisに何か利点はありますか?
アーガイル2013

14
@jeromeyers-これ@thisは単に変数名です。この場合、拡張機能を呼び出す現在のコントロールへの参照です。ソースの名前に変更したり、ボートに浮かぶものを変更したりできます。私が使用して@thisいるのは、拡張機能を呼び出す 'this Control'を参照しており、通常の(拡張機能ではない)コードで 'this'キーワードを使用することと(少なくとも頭の中で)一貫しているためです。
StyxRiver 2013

1
これはとても簡単で、私にとっては最良の解決策です。必要なすべての作業をuiスレッドに含めることができます。例:this.UIThread(()=> {txtMessage.Text = message; listBox1.Items.Add(message);});
Auto

1
私はこのソリューションが本当に好きです。マイナーニット:この方法OnUIThreadではなく、このメソッドに名前を付けUIThreadます。
ToolmakerSteve 2016年

2
そのため、この拡張機能に名前を付けましたRunOnUiThread。しかし、それは単なる個人的な好みです。
Grisgram

66

これは、これを行うための古典的な方法です。

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でスレッドをクロスして実際のコントロールを変更する必要があります...ラベルや進行状況バーなど。


62

簡単な解決策はを使用することControl.Invokeです。

void DoSomething()
{
    if (InvokeRequired) {
        Invoke(new MethodInvoker(updateGUI));
    } else {
        // Do Something
        updateGUI();
    }
}

void updateGUI() {
    // update gui here
}

シンプルにするためによくやった!シンプルなだけでなく、うまく機能します!マイクロソフトが本来の目的である単純化ができない理由を本当に理解していませんでした!メインスレッドで1行を呼び出すには、いくつかの関数を記述する必要があります。
MBH、2015

1
@MBH同意す​​る。ところで、拡張メソッドを定義する上記のstackoverflow.com/a/3588137/199364の回答に気付きましたか?カスタムユーティリティクラスで一度実行すれば、Microsoftが私たちのために実行しなかったことを気にする必要が
なくなります

@ToolmakerSteveそれはまさにそれが何を意味するのか!あなたは正しいですが、方法を見つけることができますが、DRY(繰り返さないでください)の観点から、共通の解決策がある問題は、Microsoftが最小限の労力で解決できるため、多くの時間を節約できますプログラマー:)
MBH

47

多くの場合、スレッド化コードはバグが多く、常にテストが困難です。バックグラウンドタスクからユーザーインターフェイスを更新するためにスレッドコードを記述する必要はありません。BackgroundWorkerクラスを使用してタスクを実行し、そのReportProgressメソッドを使用してユーザーインターフェイスを更新するだけです。通常、完了率を報告するだけですが、状態オブジェクトを含む別のオーバーロードがあります。文字列オブジェクトを報告するだけの例を次に示します。

    private void button1_Click(object sender, EventArgs e)
    {
        backgroundWorker1.WorkerReportsProgress = true;
        backgroundWorker1.RunWorkerAsync();
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        Thread.Sleep(5000);
        backgroundWorker1.ReportProgress(0, "A");
        Thread.Sleep(5000);
        backgroundWorker1.ReportProgress(0, "B");
        Thread.Sleep(5000);
        backgroundWorker1.ReportProgress(0, "C");
    }

    private void backgroundWorker1_ProgressChanged(
        object sender, 
        ProgressChangedEventArgs e)
    {
        label1.Text = e.UserState.ToString();
    }

同じフィールドを常に更新したい場合は問題ありません。さらに複雑な更新を行う場合は、UI状態を表すクラスを定義して、それをReportProgressメソッドに渡すことができます。

最後に、WorkerReportsProgressフラグを必ず設定ReportProgressしてください。そうしないと、メソッドが完全に無視されます。


2
処理の最後に、を介してユーザーインターフェイスを更新することもできますbackgroundWorker1_RunWorkerCompleted
DavidRR 2016

41

回答の大部分Control.Invokeは、使用されるのを待っている競合状態を使用しています。たとえば、受け入れられた答えを考えてみましょう:

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

this.Invokeが呼び出される直前にユーザーがフォームを閉じた場合(thisは、Formオブジェクトである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以降では、非同期操作のタスクを実際に使用する必要があることに注意してください。同等のタスクベースのアプローチについては、n-sanの回答を参照してください(を使用TaskScheduler.FromCurrentSynchronizationContext)。

最後に、.NET 4.5以降では、RyszardDżeganが実行時間の長い操作でUIコードを実行する必要がある場合に示されているように、Progress<T>基本的SynchronizationContext.Currentにはその作成時にキャプチャーすることもできます。


37

正しいスレッドで更新が行われることを確認する必要があります。UIスレッド。

これを行うには、直接呼び出すのではなく、イベントハンドラーを呼び出す必要があります。

これを行うには、次のようにイベントを発生させます。

(コードは頭の中でここに入力されているので、正しい構文などをチェックしていませんが、それでうまくいくはずです。)

if( MyEvent != null )
{
   Delegate[] eventHandlers = MyEvent.GetInvocationList();

   foreach( Delegate d in eventHandlers )
   {
      // Check whether the target of the delegate implements 
      // ISynchronizeInvoke (Winforms controls do), and see
      // if a context-switch is required.
      ISynchronizeInvoke target = d.Target as ISynchronizeInvoke;

      if( target != null && target.InvokeRequired )
      {
         target.Invoke (d, ... );
      }
      else
      {
          d.DynamicInvoke ( ... );
      }
   }
}

WPFコントロールはISynchronizeInvokeインターフェイスを実装しないため、上記のコードはWPFプロジェクトでは機能しないことに注意してください。

上記のコードがWindowsフォームとWPF、および他のすべてのプラットフォームで機能することを確認するためにAsyncOperationAsyncOperationManagerSynchronizationContextクラスを確認できます。

この方法でイベントを簡単に発生させるために、拡張メソッドを作成しました。これにより、次を呼び出すだけでイベントの発生を簡略化できます。

MyEvent.Raise(this, EventArgs.Empty);

もちろん、BackGroundWorkerクラスを使用することもできます。これにより、この問題が抽象化されます。


確かに、しかし、私はこの問題で私のGUIコードを「乱雑に」したくありません。私のGUIは、呼び出す必要があるかどうかを気にする必要はありません。つまり、context-swithcを実行するのはGUIの責任ではないと思います。
Frederik Gheysels 2009年

1
デリゲートを分解することなどはやり過ぎに思われます-単に次の理由だけではありません:SynchronizationContext.Current.Send(delegate {MyEvent(...);}、null);
Marc Gravell

常にSynchronizationContextにアクセスできますか?クラスがクラスlibにある場合でも、
Frederik Gheysels 2009年

29

GUIスレッドでメソッドを呼び出す必要があります。これを行うには、Control.Invokeを呼び出します。

例えば:

delegate void UpdateLabelDelegate (string message);

void UpdateLabel (string message)
{
    if (InvokeRequired)
    {
         Invoke (new UpdateLabelDelegate (UpdateLabel), message);
         return;
    }

    MyLabelControl.Text = message;
}

1
invoke行でコンパイラエラーが発生します。'System.Windows.Forms.Control.Invoke(System.Delegate、object [])'に最適なオーバーロードメソッドの一致には、無効な引数がいくつかあります
CruelIO

28

シナリオは単純であるため、実際にはステータスを調べるために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.InvokeおよびISynchronizeInvoke.BeginInvokeメソッドを使用するときに必要なマーシャリング操作を回避できます。マーシャリングテクニックの使用に問題はありませんが、注意する必要があるいくつかの警告があります。

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

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

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

3
良いアイデア。あなたが言及しなかった唯一のことは、WorkerThreadが終了したときにタイマーを適切に破棄する方法です。これが原因で、アプリケーションが終了する(つまり、ユーザーがアプリケーションを閉じる)ときに問題が発生する可能性があることに注意してください。これを解決する方法はありますか?
マット

@Matt Elapsedイベントに匿名ハンドラーを使用する代わりに、メンバーメソッドを使用して、フォームが
破棄

@ Phil1970-良い点。をSystem.Timers.ElapsedEventHandler handler = (s, a) => { MyProgressLabel.Text = m_Text; };介してそれを割り当てることを意味しm_Timer.Elapsed += handler;、後で破棄コンテキストでm_Timer.Elapsed -= handler;私は正しいですか?そして、ここで説明されているアドバイスに従って廃棄/クローズします。
マット

27

以前の回答の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
}

4
Postメソッドは内部で何を使用していると思いますか?:)
2016

23

これは、.NET Framework 3.0を使用した上記のソリューションに似ていますが、コンパイル時の安全サポートの問題を解決しました。

public  static class ControlExtension
{
    delegate void SetPropertyValueHandler<TResult>(Control souce, Expression<Func<Control, TResult>> selector, TResult value);

    public static void SetPropertyValue<TResult>(this Control source, Expression<Func<Control, TResult>> selector, TResult value)
    {
        if (source.InvokeRequired)
        {
            var del = new SetPropertyValueHandler<TResult>(SetPropertyValue);
            source.Invoke(del, new object[]{ source, selector, value});
        }
        else
        {
            var propInfo = ((MemberExpression)selector.Body).Member as PropertyInfo;
            propInfo.SetValue(source, value, null);
        }
    }
}

使用するには:

this.lblTimeDisplay.SetPropertyValue(a => a.Text, "some string");
this.lblTimeDisplay.SetPropertyValue(a => a.Visible, false);

ユーザーが間違ったデータ型を渡すと、コンパイラは失敗します。

this.lblTimeDisplay.SetPropertyValue(a => a.Visible, "sometext");

23

サルヴェーテ!この質問を検索したところ、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

21

多くの目的で、これは次のように単純です。

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

「serviceGUI()」は、フォーム(this)内のGUIレベルのメソッドであり、必要な数のコントロールを変更できます。他のスレッドから「updateGUI()」を呼び出します。パラメータを追加して値を渡すか、不安定になる可能性のあるそれらにアクセスするスレッド間で衝突が発生する可能性がある場合は、必要に応じて、それらをロックしたクラススコープ変数を(おそらくより高速に)使用します。非GUIスレッドがタイムクリティカルな場合は、InvokeではなくBeginInvokeを使用します(ブライアンギデオンの警告を念頭に置いてください)。


21

これは、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」の結果にnullチェックを追加します。
  2. 静的型安全性が向上します。

それ以外の場合、オリジナルは非常に優れたソリューションです。


21
Label lblText; //initialized elsewhere

void AssignLabel(string text)
{
   if (InvokeRequired)
   {
      BeginInvoke((Action<string>)AssignLabel, text);
      return;
   }

   lblText.Text = text;           
}

デッドロックが発生する可能性が低いため、BeginInvoke()が優先されるInvoke()ことに注意してください(ただし、ラベルにテキストを割り当てるだけの場合、これは問題ではありません)。

使用Invoke()するとき、メソッドが戻るのを待っています。ここで、呼び出されたコードでスレッドを待機する必要がある何かを実行している可能性があります。これは、呼び出しているいくつかの関数に埋め込まれているかどうかはすぐにはわかりませんが、それ自体がイベントハンドラーを介して間接的に発生する可能性があります。したがって、スレッドを待っている、スレッドはあなたを待っているし、デッドロックされています。

これにより、リリースされたソフトウェアの一部がハングしました。で置き換えることで簡単に修正できましInvoke()BeginInvoke()。戻り値が必要な場合など、同期操作が必要でない限り、を使用してくださいBeginInvoke()


20

同じ問題に遭遇したとき、私はGoogleに助けを求めましたが、簡単な解決策を提供するのではなく、例を挙げて混乱させましたMethodInvoker。だから私は自分で解決することにしました。これが私の解決策です:

次のようにデリゲートを作成します。

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(..)ここでは説明しないことになっている方法も使用できます。


17

単に次のようなものを使用してください:

 this.Invoke((MethodInvoker)delegate
            {
                progressBar1.Value = e.ProgressPercentage; // runs on UI thread
            });

を持っている場合e.ProgressPercentage、これを呼び出すメソッドからUIスレッドにすでに入っていますか?
LarsTech 2016年

ProgressChangedイベントは、UIスレッドで実行されます。これは、BackgroundWorkerを使用する便利な方法の1つです。CompletedイベントもGUIで実行されます。非UIスレッドで実行されている唯一のものは、DoWorkメソッドです。
LarsTech 2016年

15

既存のデリゲートを使用できますAction

private void UpdateMethod()
{
    if (InvokeRequired)
    {
        Invoke(new Action(UpdateMethod));
    }
}

14

私のバージョンでは、再帰的な「マントラ」を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}とおり、「この関数は別のスレッドから安全に呼び出すことができます」とわかります。


13

これを使用してラベルを更新してみてください

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

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

それはのためにあるWindowsフォーム
Kiquenet

13

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

SynchronizationContext _context;

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

var _context = SynchronizationContext.Current;

ラベルを更新する場合:

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

12

呼び出しとデリゲートを使用する必要があります

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

12

他のほとんどの答えは、この質問では私にとって少し複雑です(私はC#を初めて使用します)。

私が持っているWPFのアプリケーションを、以下のように労働者を定義しています:

問題:

BackgroundWorker workerAllocator;
workerAllocator.DoWork += delegate (object sender1, DoWorkEventArgs e1) {
    // This is my DoWork function.
    // It is given as an anonymous function, instead of a separate DoWork function

    // I need to update a message to textbox (txtLog) from this thread function

    // Want to write below line, to update UI
    txt.Text = "my message"

    // But it fails with:
    //  'System.InvalidOperationException':
    //  "The calling thread cannot access this object because a different thread owns it"
}

解決:

workerAllocator.DoWork += delegate (object sender1, DoWorkEventArgs e1)
{
    // The below single line works
    txtLog.Dispatcher.BeginInvoke((Action)(() => txtLog.Text = "my message"));
}

上記の行の意味はまだわかりませんが、うまくいきます。

以下のためのWinForms

解決:

txtLog.Invoke((MethodInvoker)delegate
{
    txtLog.Text = "my message";
});

問題は、WPFではなくWinformsに関するものでした。
Marc L.

ありがとう。上記のWinFormsソリューションを追加しました。
Manohar Reddy Poreddy

...これは、この同じ質問に対する他の多くの回答の単なるコピーですが、大丈夫です。なぜソリューションに参加せず、答えを削除するだけですか?
Marc L.

うーん、正解です。もしあなたが私の答えを注意深く読んだ場合、あなたは最初の部分(私が答えを書いた理由)を読んでいます。私の簡単な答え、そしてなぜこれがすべて起こったのかについての本当の話を予見できれば、Googleがwpfを検索してもここに私を送ります。これらの多かれ少なかれ明らかな3つの理由を逃したので、確かにあなたの反対票を削除しない理由を理解できます。大丈夫なものを掃除する代わりに、はるかに難しい何か新しいものを作成します。
Manohar Reddy Poreddy


8

たとえば、現在のスレッド以外のコントロールにアクセスします。

Speed_Threshold = 30;
textOutput.Invoke(new EventHandler(delegate
{
    lblThreshold.Text = Speed_Threshold.ToString();
}));

そこlblThresholdラベルで、Speed_Thresholdグローバル変数です。


8

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;
  }
}

これはタスク(スレッドではなく)で機能します。これは、並行コードを今すぐ作成するための好ましい方法です


1
Task.Start通常、電話はお勧め
Ohad Schneider

8

私は答えを読んだだけで、これは非常にホットなトピックのようです。現在.NET 3.5 SP1とWindowsフォームを使用しています。

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スレッドがFormHandleを作成するのにかかる時間よりも、ラベルの更新前の操作に「かかる時間が短い」(それを読み取って簡略化として解釈する)場合、InvalidOperationExceptionが発生する可能性があります。これは、ShowDialog()メソッド内で発生します。

次のようにハンドルも確認する必要があります。

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を更新し、オプションで明確に定義された時間(タイムアウト)だけ待機するようにします。


1
IsHandleCreatedもチェックしているので、これを見つけるためにここに来ました。チェックするもう1つのプロパティはIsDisposedです。フォームが破棄されている場合、そのフォームでInvoke()を呼び出すことはできません。バックグラウンドスレッドが完了する前にユーザーがフォームを閉じた場合、フォームが破棄されたときにUIにコールバックしようとしないでください。
ジョン

最初から始めるのは悪い考えだと思います。通常、子フォームをすぐに表示し、バックグラウンド処理中にプログレスバーやその他のフィードバックを取得します。または、最初にすべての処理を実行してから、作成時に結果を新しいフォームに渡します。両方を同時に実行すると、一般にわずかな利点がありますが、コードの保守性ははるかに低くなります。
Phil1970 16

説明されているシナリオでは、バックグラウンドスレッドジョブの進行状況ビューとして使用されるモーダルフォームが考慮されています。モーダルである必要があるため、Form.ShowDialog()メソッドを呼び出して表示する必要があります。これにより、フォームが閉じるまで、呼び出しに続くコードが実行されないようにします。したがって、特定の例とは異なる方法でバックグラウンドスレッドを開始できない限り(そしてもちろん可能です)、このフォームは、バックグラウンドスレッドの開始後にモーダルに表示する必要があります。この場合、作成されるハンドルを確認する必要があります。モーダルフォームが必要ない場合は、別の話です。
2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.