をLabel
別のものから更新する最も簡単な方法はどれThread
ですか?
私はで
Form
実行してthread1
おり、そこから別のスレッド(thread2
)を開始しています。thread2
がいくつかのファイルを処理している間Label
に、の作業のForm
現在のステータスでを更新したいと思いますthread2
。
どうすればできますか?
をLabel
別のものから更新する最も簡単な方法はどれThread
ですか?
私はでForm
実行してthread1
おり、そこから別のスレッド(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年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バージョンよりもはるかに優れています。
コンパイル時の安全のためにこのコードを改善する方法について他に提案がある場合は、コメントしてください!
SetControlPropertyThreadSafe(myLabel, "Text", status)
を別のモジュール、クラス、フォームから呼び出すことができます
最も簡単な方法は、に渡される匿名メソッドです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には多くのコンテンツがあります。
以来、.NET 4.5とC#5.0あなたが使用する必要があるタスクベースの非同期パターン(TAP)と一緒に非同期 - のawaitキーワードをすべての分野で(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#の未来を参照してください。ジョセフアルバハリによって「待っている」人には良いことが起こります。
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...");
}
}
SecondThreadConcern.LongWork()
例外をスローした場合、UIスレッドでキャッチできますか?これは素晴らしい投稿です。
Task.Delay(500).Wait()
?現在のスレッドをブロックするだけのタスクを作成する意味は何ですか?スレッドプールスレッドをブロックしないでください。
.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
this.refresh()
GUIを強制的に無効にして再描画する必要があることがわかりました..それが役に立った場合は..
.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");
@this
は単に変数名です。この場合、拡張機能を呼び出す現在のコントロールへの参照です。ソースの名前に変更したり、ボートに浮かぶものを変更したりできます。私が使用して@this
いるのは、拡張機能を呼び出す 'this Control'を参照しており、通常の(拡張機能ではない)コードで 'this'キーワードを使用することと(少なくとも頭の中で)一貫しているためです。
OnUIThread
ではなく、このメソッドに名前を付けUIThread
ます。
RunOnUiThread
。しかし、それは単なる個人的な好みです。
これは、これを行うための古典的な方法です。
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
してください。そうしないと、メソッドが完全に無視されます。
backgroundWorker1_RunWorkerCompleted
。
回答の大部分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
にはその作成時にキャプチャーすることもできます。
正しいスレッドで更新が行われることを確認する必要があります。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;
}
シナリオは単純であるため、実際にはステータスを調べるために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
or Control.BeginInvoke
アプローチとは対照的に、疎結合のままです。Elapsed
イベントに匿名ハンドラーを使用する代わりに、メンバーメソッドを使用して、フォームが
以前の回答の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
}
これは、.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
多くの目的で、これは次のように単純です。
public delegate void serviceGUIDelegate();
private void updateGUI()
{
this.Invoke(new serviceGUIDelegate(serviceGUI));
}
「serviceGUI()」は、フォーム(this)内のGUIレベルのメソッドであり、必要な数のコントロールを変更できます。他のスレッドから「updateGUI()」を呼び出します。パラメータを追加して値を渡すか、不安定になる可能性のあるそれらにアクセスするスレッド間で衝突が発生する可能性がある場合は、必要に応じて、それらをロックしたクラススコープ変数を(おそらくより高速に)使用します。非GUIスレッドがタイムクリティカルな場合は、InvokeではなくBeginInvokeを使用します(ブライアンギデオンの警告を念頭に置いてください)。
これは、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!")
それ以外の場合、オリジナルは非常に優れたソリューションです。
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()
。
同じ問題に遭遇したとき、私は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(..)
ここでは説明しないことになっている方法も使用できます。
単に次のようなものを使用してください:
this.Invoke((MethodInvoker)delegate
{
progressBar1.Value = e.ProgressPercentage; // runs on UI thread
});
e.ProgressPercentage
、これを呼び出すメソッドからUIスレッドにすでに入っていますか?
既存のデリゲートを使用できますAction
:
private void UpdateMethod()
{
if (InvokeRequired)
{
Invoke(new Action(UpdateMethod));
}
}
私のバージョンでは、再帰的な「マントラ」を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}
とおり、「この関数は別のスレッドから安全に呼び出すことができます」とわかります。
これを使用してラベルを更新してみてください
public static class ExtensionMethods
{
private static Action EmptyDelegate = delegate() { };
public static void Refresh(this UIElement uiElement)
{
uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
}
}
他のほとんどの答えは、この質問では私にとって少し複雑です(私は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";
});
私が考える最も簡単な方法:
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;
}
}
これはタスク(スレッドではなく)で機能します。これは、並行コードを今すぐ作成するための好ましい方法です。
Task.Start
通常、電話はお勧め
私は答えを読んだだけで、これは非常にホットなトピックのようです。現在.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スレッドがFormのHandleを作成するのにかかる時間よりも、ラベルの更新前の操作に「かかる時間が短い」(それを読み取って簡略化として解釈する)場合、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を更新し、オプションで明確に定義された時間(タイムアウト)だけ待機するようにします。