現在のスレッドがUIスレッドであることを確認するアサーションメソッドEnsure.CurrentlyOnUiThread()を作成しました。
Ensure.cs
using System.Diagnostics;
using System.Windows.Forms;
public static class Ensure
{
[Conditional("DEBUG")]
public static void CurrentlyOnUiThread()
{
if (!Application.MessageLoop)
{
throw new ThreadStateException("Assertion failed: not on the UI thread");
}
}
}
使用しないでください
_if(Dispatcher.CurrentDispatcher.Thread == Thread.CurrentThread)
{
// Do something
}
_
_Dispatcher.CurrentDispatcher
_は、現在のスレッドにディスパッチャがない場合、現在のスレッドに関連付けられた新しいDispatcher
を作成して返します。
代わりにこのようにします
_Dispatcher dispatcher = Dispatcher.FromThread(Thread.CurrentThread);
if (dispatcher != null)
{
// We know the thread have a dispatcher that we can use.
}
_
正しいディスパッチャを持っているか、正しいスレッドにいることを確認するには、次のオプションがあります
_Dispatcher _myDispatcher;
public void UnknownThreadCalling()
{
if (_myDispatcher.CheckAccess())
{
// Calling thread is associated with the Dispatcher
}
try
{
_myDispatcher.VerifyAccess();
// Calling thread is associated with the Dispatcher
}
catch (InvalidOperationException)
{
// Thread can't use dispatcher
}
}
_
CheckAccess()
およびVerifyAccess()
は、インテリセンスには表示されません。
また、これらの種類のものに頼らなければならない場合は、おそらくデザインが悪いためです。どのスレッドがプログラムのどのコードを実行するかを知っておく必要があります。
WinForms内では、通常使用します
if(control.InvokeRequired)
{
// Do non UI thread stuff
}
wPF用
if (!control.Dispatcher.CheckAccess())
{
// Do non UI Thread stuff
}
ジェネリック制約を使用して、これらのどれを呼び出す必要があるかを決定する小さなメソッドをおそらく作成します。例えば.
public static bool CurrentlyOnUiThread<T>(T control)
{
if(T is System.Windows.Forms.Control)
{
System.Windows.Forms.Control c = control as System.Windows.Forms.Control;
return !c.InvokeRequired;
}
else if(T is System.Windows.Controls.Control)
{
System.Windows.Controls.Control c = control as System.Windows.Control.Control;
return c.Dispatcher.CheckAccess()
}
}
WPFの場合:
// You are on WPF UI thread!
if (Thread.CurrentThread == System.Windows.Threading.Dispatcher.CurrentDispatcher.Thread)
WinFormsの場合:
// You are NOT on WinForms UI thread for this control!
if (someControlOrWindow.InvokeRequired)
WPFでは、次を使用します。
public static void InvokeIfNecessary (Action action)
{
if (Thread.CurrentThread == Application.Current.Dispatcher.Thread)
action ();
else {
Application.Current.Dispatcher.Invoke(action);
}
}
キーは、Dispatcher.CurrentDispatcher(現在のスレッドのディスパッチャを提供します)をチェックする代わりに、現在のスレッドがアプリケーションまたは別のコントロールのディスパッチャと一致するかどうかを確認する必要があります。
多分 Control.InvokeRequired
(WinForms)およびDispatcher.CheckAccess
(WPF)は大丈夫ですか?
UIの知識をロジックにプッシュしています。これは良い設計ではありません。
UIスレッドが乱用されないようにすることはUIの範囲内であるため、UIレイヤーはスレッドを処理する必要があります。
これにより、winformsで IsInvokeRequired およびWPF ...で Dispatcher.Invoke を使用できるようになり、同期および非同期asp.netリクエスト内でもコードを使用できるようになります。 ..
実際には、アプリケーションロジック内の低レベルでスレッドを処理しようとすると、多くの不必要な複雑さが追加されることがわかりました。実際、実際にはフレームワーク全体がこの点を認めて書かれています。フレームワークのほとんどがスレッドセーフではありません。スレッドの安全性を確保するために(より高いレベルで)呼び出し元まで。
WPFの場合:
私のスレッドのDispatcherが実際に開始されているかどうかを知る必要があります。スレッドでWPFクラスを作成すると、Dispatcher.Run()
を実行したことがない場合でも、受け入れられた回答にディスパッチャが存在することが示されるためです。私はいくつかの反射で終わった:
public static class WpfDispatcherUtils
{
private static readonly Type dispatcherType = typeof(Dispatcher);
private static readonly FieldInfo frameDepthField = dispatcherType.GetField("_frameDepth", BindingFlags.Instance | BindingFlags.NonPublic);
public static bool IsInsideDispatcher()
{
// get dispatcher for current thread
Dispatcher currentThreadDispatcher = Dispatcher.FromThread(Thread.CurrentThread);
if (currentThreadDispatcher == null)
{
// no dispatcher for current thread, we're definitely outside
return false;
}
// get current dispatcher frame depth
int currentFrameDepth = (int) frameDepthField.GetValue(currentThreadDispatcher);
return currentFrameDepth != 0;
}
}
以下は、非UIスレッドからUIプロパティ(INotifyPropertyChangedを実装する)を変更する試みをキャッチするためにWPFで使用するコードのスニペットです。
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
// Uncomment this to catch attempts to modify UI properties from a non-UI thread
//bool oopsie = false;
//if (Thread.CurrentThread != Application.Current.Dispatcher.Thread)
//{
// oopsie = true; // place to set a breakpt
//}
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
MVVMを使用すると、実際には非常に簡単です。私がやることは、たとえばViewModelBaseに次のようなものを入れることです...
protected readonly SynchronizationContext SyncContext = SynchronizationContext.Current;
または...
protected readonly TaskScheduler Scheduler = TaskScheduler.Current;
次に、特定のViewModelが「観測可能な」何かに触れる必要がある場合、コンテキストを確認してそれに応じて反応することができます...
public void RefreshData(object state = null /* for direct calls */)
{
if (SyncContext != SynchronizationContext.Current)
{
SyncContext.Post(RefreshData, null); // SendOrPostCallback
return;
}
// ...
}
または、コンテキストに戻る前にバックグラウンドで何か他のことを行います...
public void RefreshData()
{
Task<MyData>.Factory.StartNew(() => GetData())
.ContinueWith(t => {/* Do something with t.Result */}, Scheduler);
}
通常、MVVM(またはその他のアーキテクチャ)を整然とフォローしている場合、UI同期の責任がどこにあるかを簡単に知ることができます。ただし、基本的にはどこでもこれを実行して、オブジェクトが作成されたコンテキストに戻ることができます。大規模で複雑なシステムでこれをクリーンかつ一貫して処理するための「ガード」を簡単に作成できると確信しています。
あなたの唯一の責任はあなた自身の元の文脈に戻ることであると言うのは理にかなっていると思います。同じことをするのはクライアントの責任です。