別のスレッドからLabel
を更新する最も簡単な方法は何ですか?
私はthread1
にForm
を持っていて、それから私は別のスレッド(thread2
)を始めています。 thread2
がいくつかのファイルを処理している間、私はLabel
のForm
を現在のthread2
の作業のステータスで更新したいと思います。
どうやってやるの?
.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/10/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 ||
[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バージョンよりはずっと優れています。
このコードをコンパイル時の安全性のために改善する方法について他に提案がある場合は、コメントしてください。
最も単純な の方法は 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
は完了するまで実行をブロックします - これは同期コードです。質問では非同期コードについては尋ねられませんが、 スタックオーバーフローに関するコンテンツが多数あります 非同期コードについて知りたい場合の作成について.
。NET 4.5およびC#5. なので、 Task-based Asynchronous Pattern(TAP) とともに async - await キーワード in all area (GUIを含む):
TAPは、新規開発に推奨される非同期設計パターンです
非同期プログラミングモデル(APM) および イベントベースの非同期パターン(EAP) の代わりに(後者には BackgroundWorkerクラス が含まれます)。
次に、新規開発の推奨ソリューションは次のとおりです。
イベントハンドラーの非同期実装(はい、それだけです):
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";
}
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());
}
}
}
次のことに注意してください。
より詳細な例については、以下を参照してください。 C#の未来:「待つ」人には良いことが来る by Joseph Albahari 。
I Threading Model コンセプトについても参照してください。
以下のスニペットは、バックグラウンド実行中に複数のクリックを防ぐために例外を処理し、ボタンの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...");
}
}
.NET 4用の Marc Gravellの 最も単純な solution のバリエーション:
control.Invoke((MethodInvoker) (() => control.Text = "new text"));
または、代わりにActionデリゲートを使用してください。
control.Invoke(new Action(() => control.Text = "new text"));
2つの比較については、こちらを参照してください。 MethodInvokerとControl.BeginInvokeのアクション
.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");
これはあなたがこれをすべき古典的な方法です:
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では、ラベルやプログレスバーのように実際のコントロールを変更するためにスレッドをクロスする必要があります。
簡単な解決策はControl.Invoke
を使うことです。
void DoSomething()
{
if (InvokeRequired) {
Invoke(new MethodInvoker(updateGUI));
} else {
// Do Something
updateGUI();
}
}
void updateGUI() {
// update gui here
}
スレッドコードはバグが多く、テストが難しいです。バックグラウンドタスクからユーザーインターフェイスを更新するためにスレッドコードを書く必要はありません。 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
メソッドは完全に無視されます。
回答の大多数は、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以降では、非同期操作にタスクを実際に使用しているはずです。同等のタスクベースのアプローチ(TaskScheduler.FromCurrentSynchronizationContext
を使用)については、 n-sanの answerを参照してください。
最後に、.NET 4.5以降では、 RyszardDżegan's で示されているようにProgress<T>
(基本的には作成時にSynchronizationContext.Current
をキャプチャします)を使用できます。
更新が正しいスレッドで行われるようにする必要があります。 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、そして他のすべてのプラットフォームで動作することを確認するために、AsyncOperation
、AsyncOperationManager
およびSynchronizationContext
クラスを見てください。
このようにしてイベントを簡単に発生させるために、拡張メソッドを作成しました。これを呼び出すことでイベントの発生を簡単にすることができます。
MyEvent.Raise(this, EventArgs.Empty);
もちろん、BackGroundWorkerクラスを利用することもできます。これはこの問題を抽象化します。
GUIスレッドでメソッドを呼び出す必要があります。あなたはControl.Invokeを呼び出すことによってそれを行うことができます。
例えば:
delegate void UpdateLabelDelegate (string message);
void UpdateLabel (string message)
{
if (InvokeRequired)
{
Invoke (new UpdateLabelDelegate (UpdateLabel), message);
return;
}
MyLabelControl.Text = message;
}
前の回答の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
}
シナリオが些細なので、私は実際に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スレッドがデータをポーリングします。これは多くのシナリオで使用される一般的なパターンです。あなたがしたいのはワーカースレッドから進捗情報を表示することだけなので、私はあなたがこの解決策がマーシャリング解決策に代わる素晴らしい方法であることをあなたが見つけると思います。次のような利点があります。
Control.Invoke
またはControl.BeginInvoke
のアプローチとは対照的に、疎結合のままです。これは.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");
サルベテ!この質問を検索したところ、 FrankG および Oregon 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
これは、C#3.0のIan Kempのソリューションのバリエーションです。
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!")
そうでなければ、オリジナルは非常にいい解決策です。
多くの目的のためにそれはこれと同じくらい簡単です:
public delegate void serviceGUIDelegate();
private void updateGUI()
{
this.Invoke(new serviceGUIDelegate(serviceGUI));
}
"serviceGUI()"はフォーム(this)内のGUIレベルのメソッドで、必要なだけコントロールを変更できます。他のスレッドから "updateGUI()"を呼び出します。値を渡すためにパラメータを追加したり、アクセスするスレッド間で衝突が発生して不安定になる可能性がある場合は、必要に応じてクラススコープ変数をロックして使用します。非GUIスレッドがタイムクリティカルである場合は、Invokeの代わりにBeginInvokeを使用してください(Brian Gideonの警告に留意してください)。
私が同じ問題に遭遇したとき、私はグーグルから助けを求めました、しかし私に簡単な解決策を与えるよりむしろそれは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(..)
メソッドを使用することもできます。これについてはここでは説明しません。
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()
を使用してください。
このようなものを使うだけです:
this.Invoke((MethodInvoker)delegate
{
progressBar1.Value = e.ProgressPercentage; // runs on UI thread
});
あなたは既存のデリゲートAction
を使うことができます:
private void UpdateMethod()
{
if (InvokeRequired)
{
Invoke(new Action(UpdateMethod));
}
}
私のバージョンは 一行を挿入することです 再帰的な "mantra":/
引数がない場合:
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!
}
それだ 。
いくつかの議論 :通常、コードを読みやすくするために、if ()
ステートメントの後に{}を1行に入れるのは悪いことです。しかし、この場合それは日常的なまったく同じ「マントラ」です。この方法がプロジェクト全体で一貫している場合でも、コードの可読性が損なわれることはありません。そしてそれはあなたのコードをポイ捨て(5行ではなく1行のコード)から節約します。
if(InvokeRequired) {something long}
を見ればわかるとおり、「この関数は他のスレッドから呼び出しても安全です」ということだけです。
これを使ってラベルを更新しよう
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);
あなたはinvokeとdelegateを使わなければなりません
private delegate void MyLabelDelegate();
label1.Invoke( new MyLabelDelegate(){ label1.Text += 1; });
他の答えの大部分はこの質問に関して私にとって少し複雑です(私は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";
});
たとえば、現在のスレッド以外のコントロールにアクセスします。
Speed_Threshold = 30;
textOutput.Invoke(new EventHandler(delegate
{
lblThreshold.Text = Speed_Threshold.ToString();
}));
lblThreshold
はLabel、Speed_Threshold
はグローバル変数です。
私はちょうど答えを読み、これは非常に話題のようです。私は現在.NET 3.5 SP1とWindowsフォームを使用しています。
InvokeRequiredプロパティを使用する、以前の回答で広く説明されている一般的な式は、ほとんどのケースをカバーしますが、プール全体をカバーするわけではありません。
Handleがまだ作成されていない場合はどうなりますか?
InvokeRequiredプロパティは、説明されているように こちら(MSDNのControl.InvokeRequiredプロパティ参照) がGUIスレッドではないスレッドから呼び出された場合はtrueを返します。 GUIスレッド、またはHandleがまだ作成されていない場合.
モーダルフォームを別のスレッドで表示および更新したい場合は、例外が発生する可能性があります。そのフォームをモーダルで表示したいので、次のことができます。
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スレッドがFormを作成するのにかかる時間よりも「時間がかからず」(単純化されたものとして解釈して)解釈する場合、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!";
}
Handleがまだ作成されていない場合に実行する操作を処理できます。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がどのスレッドが呼び出しを行うかに関わらず、更新されることを確信しています。オプションで、明確に定義された時間(タイムアウト)を待ちます。
あなたが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;
}
}
これは 今のところ並行コードを書くための望ましい方法 であるタスク(スレッドではない)に働きます。
私が考える最も簡単な方法:
void Update()
{
BeginInvoke((Action)delegate()
{
//do your update
});
}
WPFアプリケーションで最も簡単な方法は次のとおりです。
this.Dispatcher.Invoke((Action)(() =>
{
// This refers to a form in a WPF application
val1 = textBox.Text; // Access the UI
}));
操作に時間がかかる場合でも(この例ではthread.sleep) - このコードはUIをロックしません。
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(ThreadJob));
t.IsBackground = true;
t.Start();
}
private void ThreadJob()
{
string newValue= "Hi";
Thread.Sleep(2000);
this.Invoke((MethodInvoker)delegate
{
label1.Text = newValue;
});
}
この醜い実装の背後にマイクロソフトの論理を入れることはできませんでしたが、2つの機能が必要です。
void setEnableLoginButton()
{
if (InvokeRequired)
{
// btn_login can be any conroller, (label, button textbox ..etc.)
btn_login.Invoke(new MethodInvoker(setEnable));
// OR
//Invoke(new MethodInvoker(setEnable));
}
else {
setEnable();
}
}
void setEnable()
{
btn_login.Enabled = isLoginBtnEnabled;
}
これらのスニペットは私のために機能するので、私は別のスレッドで何かをすることができ、それから私はGUIを更新します:
Task.Factory.StartNew(()=>
{
// THIS IS NOT GUI
Thread.Sleep(5000);
// HERE IS INVOKING GUI
btn_login.Invoke(new Action(() => DoSomethingOnGUI()));
});
private void DoSomethingOnGUI()
{
// GUI
MessageBox.Show("message", "title", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
もっと簡単です:
btn_login.Invoke(new Action(()=>{ /* HERE YOU ARE ON GUI */ }));
簡単な解決策の中にはInvokeRequired
チェックを省略しているものがあることに気付いたので、警告を追加したいと思いました。
私は、あなたのコードが コントロールのウィンドウハンドルが作成される前に を実行すると(例えばフォームが表示される前に)、Invoke
が例外を投げることに気づきました。そのため、InvokeRequired
またはInvoke
を呼び出す前に、常にBeginInvoke
をチェックすることをお勧めします。
これは、より機能的なスタイルを使用した、古くからの問題についての新しい外観です。すべてのプロジェクトでTaskXMクラスを保持している場合は、スレッド間の更新について再度心配する必要がないように1行のコードしかありません。
public class Example
{
/// <summary>
/// No more delegates, background workers, etc. Just one line of code as shown below.
/// Note it is dependent on the Task Extension method shown next.
/// </summary>
public async void Method1()
{
// Still on the GUI thread here if the method was called from the GUI thread
// This code below calls the extension method which spins up a new task and calls back.
await TaskXM.RunCodeAsync(() =>
{
// Running an asynchronous task here
// Cannot update the GUI thread here, but can do lots of work
});
// Can update GUI on this line
}
}
/// <summary>
/// A class containing extension methods for the Task class
/// </summary>
public static class TaskXM
{
/// <summary>
/// RunCodeAsyc is an extension method that encapsulates the Task.run using a callback
/// </summary>
/// <param name="Code">The caller is called back on the new Task (on a different thread)</param>
/// <returns></returns>
public async static Task RunCodeAsync(Action Code)
{
await Task.Run(() =>
{
Code();
});
return;
}
}
多少多すぎるかもしれませんが、これは私がこれを普通に解決する一種の方法です:
同期のため、呼び出しはここでは必要ありません。 BasicClassThreadExampleは私にとっては一種のレイアウトなので、実際のニーズに合わせて変更してください。
UIスレッドで処理する必要がないので簡単です。
public partial class Form1 : Form
{
BasicClassThreadExample _example;
public Form1()
{
InitializeComponent();
_example = new BasicClassThreadExample();
_example.MessageReceivedEvent += _example_MessageReceivedEvent;
}
void _example_MessageReceivedEvent(string command)
{
listBox1.Items.Add(command);
}
private void button1_Click(object sender, EventArgs e)
{
listBox1.Items.Clear();
_example.Start();
}
}
public class BasicClassThreadExample : IDisposable
{
public delegate void MessageReceivedHandler(string msg);
public event MessageReceivedHandler MessageReceivedEvent;
protected virtual void OnMessageReceivedEvent(string msg)
{
MessageReceivedHandler handler = MessageReceivedEvent;
if (handler != null)
{
handler(msg);
}
}
private System.Threading.SynchronizationContext _SynchronizationContext;
private System.Threading.Thread _doWorkThread;
private bool disposed = false;
public BasicClassThreadExample()
{
_SynchronizationContext = System.ComponentModel.AsyncOperationManager.SynchronizationContext;
}
public void Start()
{
_doWorkThread = _doWorkThread ?? new System.Threading.Thread(dowork);
if (!(_doWorkThread.IsAlive))
{
_doWorkThread = new System.Threading.Thread(dowork);
_doWorkThread.IsBackground = true;
_doWorkThread.Start();
}
}
public void dowork()
{
string[] retval = System.IO.Directory.GetFiles(@"C:\Windows\System32", "*.*", System.IO.SearchOption.TopDirectoryOnly);
foreach (var item in retval)
{
System.Threading.Thread.Sleep(25);
_SynchronizationContext.Post(new System.Threading.SendOrPostCallback(delegate(object obj)
{
OnMessageReceivedEvent(item);
}), null);
}
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
_doWorkThread.Abort();
}
disposed = true;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~BasicClassThreadExample() { Dispose(false); }
}
このテーマに関するもう1つの例:私は、共通のメソッド実装を含む抽象クラスUiSynchronizeModelを作成しました。
public abstract class UiSynchronizeModel
{
private readonly TaskScheduler uiSyncContext;
private readonly SynchronizationContext winformsOrDefaultContext;
protected UiSynchronizeModel()
{
this.winformsOrDefaultContext = SynchronizationContext.Current ?? new SynchronizationContext();
this.uiSyncContext = TaskScheduler.FromCurrentSynchronizationContext();
}
protected void RunOnGuiThread(Action action)
{
this.winformsOrDefaultContext.Post(o => action(), null);
}
protected void CompleteTask(Task task, TaskContinuationOptions options, Action<Task> action)
{
task.ContinueWith(delegate
{
action(task);
task.Dispose();
}, CancellationToken.None, options, this.uiSyncContext);
}
}
モデルまたはコントローラクラスはこの抽象クラスから派生する必要があります。任意のパターン(タスクまたは手動で管理されたバックグラウンドスレッド)を使用して、次のようにこれらのメソッドを使用することができます。
public void MethodThatCalledFromBackroundThread()
{
this.RunOnGuiThread(() => {
// Do something over UI controls
});
}
タスクの例:
var task = Task.Factory.StartNew(delegate
{
// Background code
this.RunOnGuiThread(() => {
// Do something over UI controls
});
});
this.CompleteTask(task, TaskContinuationOptions.OnlyOnRanToCompletion, delegate
{
// Code that can safely use UI controls
});
基本的に、フレームワークのバージョンやGUIの基礎となるライブラリの種類に関係なくこの問題を解決する方法は、ワーカースレッドからGUIのスレッドメッセージキューへのコントロールの関連操作を整列化するスレッドの同期コンテキストを作成するコントロールを保存することです。
例:
SynchronizationContext ctx = SynchronizationContext.Current; // From control
ctx.Send\Post... // From worker thread
そしてもう1つの一般的な Control 拡張に近い..
最初に Control 型のオブジェクトの拡張メソッドを追加してください。
public static void InvokeIfRequired<T>(this T c, Action<T> action) where T : Control
{
if (c.InvokeRequired)
{
c.Invoke(new Action(() => action(c)));
}
else
{
action(c);
}
}
uIスレッドでobject1という名前のコントロールにアクセスするには、別のスレッドから次のように呼び出します。
object1.InvokeIfRequired(c => { c.Visible = true; });
object1.InvokeIfRequired(c => { c.Text = "ABC"; });
..またはこんな感じ
object1.InvokeIfRequired(c =>
{
c.Text = "ABC";
c.Visible = true;
}
);
最初にフォームのインスタンス(この場合はmainForm)を取得してから、別のスレッドでこのコードを使用します。
mainForm.Invoke(new MethodInvoker(delegate ()
{
// Update things in my mainForm here
mainForm.UpdateView();
}));
私はこれを好む:
private void UpdateNowProcessing(string nowProcessing)
{
if (this.InvokeRequired)
{
Action<string> d = UpdateNowProcessing;
Invoke(d, nowProcessing);
}
else
{
this.progressDialog.Next(nowProcessing);
}
}
値を保持するために、共通の変数を別のクラスに配置します。
例:
public class data_holder_for_controls
{
// It will hold the value for your label
public string status = string.Empty;
}
class Demo
{
public static data_holder_for_controls d1 = new data_holder_for_controls();
static void Main(string[] args)
{
ThreadStart ts = new ThreadStart(perform_logic);
Thread t1 = new Thread(ts);
t1.Start();
t1.Join();
//your_label.Text=d1.status; --- can access it from any thread
}
public static void perform_logic()
{
// Put some code here in this function
for (int i = 0; i < 10; i++)
{
// Statements here
}
// Set the result in the status variable
d1.status = "Task done";
}
}
単にUIの同期コンテキストを使用する
using System.Threading;
// ...
public partial class MyForm : Form
{
private readonly SynchronizationContext uiContext;
public MyForm()
{
InitializeComponent();
uiContext = SynchronizationContext.Current; // get ui thread context
}
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(() =>
{// set ui thread context to new thread context
// for operations with ui elements to be performed in proper thread
SynchronizationContext
.SetSynchronizationContext(uiContext);
label1.Text = "some text";
});
t.Start();
}
}
私の場合(WPF)では、解決策は次のように単純です。
private void updateUI()
{
if (!Dispatcher.CheckAccess())
{
Dispatcher.BeginInvoke(updateUI);
return;
}
// Update any number of controls here
}
一般的な方法は次のとおりです。
using System;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
int clickCount = 0;
public Form1()
{
InitializeComponent();
label1.SetText("0");
}
private void button1_Click(object sender, EventArgs e)
{
new Thread(() => label1.SetText((++clickCount).ToString())).Start();
}
}
public static class ControlExtensions
{
public static void SetText(this Control control, string text)
{
if (control.InvokeRequired)
control.Invoke(setText, control, text);
else
control.Text = text;
}
private static readonly Action<Control, string> setText =
(control, text) => control.Text = text;
}
}
説明 :
答えは これ のようなものです。しかし(私にとっては)よりきれいで新しい構文を使用しています。ポイントはInvokeRequired
のcontrol
プロパティです。呼び出し元はコントロールが作成されたスレッドとは異なるスレッド上にあるため、呼び出し元がコントロールの呼び出しを行うときに呼び出しメソッドを呼び出す必要があるかどうかを示す値を取得します。したがって、control
が作成されたのと同じスレッドでcontrol.SetText("some text")
を呼び出す場合は、Text
をこのcontrol.Text = text
に設定するだけでOKです。しかし、他のスレッドではSystem.InvalidOperationException
が発生するため、Text
が作成されたスレッドにcontrol
を設定するためにcontrol.Invoke(...)
を介してメソッドを呼び出す必要があります。
最も簡単な方法は次のように呼び出すことです。
Application.Current.Dispatcher.Invoke(new Action(() =>
{
try
{
///
}
catch (Exception)
{
//
}
}
));