SynchronizationContextの作成

この話は、私がUIストリームからではなくUIを最初に操作しようとしたときに始まりました。 そして、さまざまな「グリッチ」をキャッチし始めたとき、これを慎重に行う必要があることに気付きました。 後に、私は世界でこれに出くわし、SynchronizationContextに最初に出会ったのはその瞬間でした。 しかし、その後、このオブジェクトのデバイスについて読んだ後、私はこの知識で十分であると考えました。 これは、たとえばここで実行できます: SynchronizationContext-MSDNが失敗した場合



私はc#5の出力とそのasync / awaitのみでSynchronizationContextを思い出しました。 このメカニズムは、このまさに同期コンテキストと相互作用します。 これは、非同期操作の後に、コードを非同期操作の原因となるスレッドで実行できるようにするためです。これは、UIを操作するときに非常に便利です。 しかし、この小さなコードをUIスレッドなどで実行すると:



Debug.WriteLine(Thread.CurrentThread.ManagedThreadId); await Task.Run(() => Debug.WriteLine(Thread.CurrentThread.ManagedThreadId)); Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
      
      





UIストリームで起動した場合にのみ、コードが元のストリームに戻ることがわかります。 問題は、同期コンテキストがUIストリームでのみ設定されることです(wcfなどを除く)。 すぐに考えが浮かびます。同期コンテキストを目的のスレッドに設定するだけです。 しかし、ここでは問題が待っています。SynchronizationContextの標準実装では必要な機能が提供されません。 これにより、現在のスレッドまたはプールのスレッドで引き続きコードを実行できます。 コピーして実行して目的の結果を簡単に確認できる実装が見つからなかったので、自分で実装して、実際にどのように見えるかを想像することにしました。 これについては以下で説明します。



コードを実行するために、SynchronizationContextには2つの仮想メソッドSend(同期実行)とPost(非同期)があります。 したがって、SynchronizationContextから継承し、必要なメソッドを再定義します。



CustomSynchronizationContext
 class CustomSynchronizationContext : SynchronizationContext, IDisposable { private readonly AutoResetEvent _eventReset; private readonly Queue<KeyValuePair<SendOrPostCallback, object>> _workItems; private readonly Thread _thread; public CustomSynchronizationContext() { _eventReset = new AutoResetEvent(false); _workItems = new Queue<KeyValuePair<SendOrPostCallback, object>>(); _thread = new Thread(DoWork); _thread.Start(this); } private void DoWork(object obj) { SynchronizationContext.SetSynchronizationContext(obj as SynchronizationContext); while (true) { while (_workItems.Count > 0) { var item = _workItems.Dequeue(); item.Key(item.Value); } _eventReset.Reset(); _eventReset.WaitOne(); } } public override void Post(SendOrPostCallback d, object state) { _workItems.Enqueue(new KeyValuePair<SendOrPostCallback, object>(d, state)); _eventReset.Set(); } public void Dispose() { _eventReset.Dispose(); _thread.Abort(); } }
      
      







始めます。



 static void Main(string[] args) { var syncContext = new CustomSynchronizationContext(); Console.WriteLine(Thread.CurrentThread.ManagedThreadId); syncContext.Post(o => Console.WriteLine(Thread.CurrentThread.ManagedThreadId), null); }
      
      





そして予想どおり、さまざまなフローが見られます。 ここで何が起こっていますか? まず、便宜上、ストリームのコンテキストではなく、コンテキスト内でストリームを作成して割り当てます。 ですから、私たち以外の誰もこの流れに影響を与えることはできないでしょう。 次に、作成されたスレッドで実行するデリゲートを格納するキューを開始します。 第三に、AutoResetEventを「固定」して、スレッドが終了せず、サイクルを繰り返さないようにします。 まあ、IDisposable。 コンテキストを削除するとき、フローを削除する試みもあることに注意してください。 つまり そのようなコード:



 static void Main(string[] args) { using (var syncContext = new CustomSynchronizationContext()) { Console.WriteLine(Thread.CurrentThread.ManagedThreadId); syncContext.Post(o => Console.WriteLine(Thread.CurrentThread.ManagedThreadId), null); } }
      
      





ほとんどの場合、元のストリームに関する情報のみが表示されます。 おそらくこれは私たちが望んでいることではないかもしれませんが、デモのために、私はそう思うでしょう。 修正も簡単です。



例外処理はどうですか? ご覧ください。



 static void Main(string[] args) { var syncContext = new CustomSynchronizationContext(); try { syncContext.Post(o => { throw new Exception("TestException"); }, null); } catch (Exception ex) { Console.WriteLine(ex.Message); } }
      
      





私たちの「特別な」ストリームに落ちると予想されます。 同期実行を担当するSendメソッドもあることを思い出してください。 これにより、デリゲートが完了するまで待機し、例外を取得できます。 やってみましょう。



CustomSynchronizationContext(最終デモ)
 class CustomSynchronizationContext : SynchronizationContext, IDisposable { private readonly AutoResetEvent _workerResetEvent; private readonly ConcurrentQueue<WorkItem> _workItems; private readonly Thread _thread; public CustomSynchronizationContext() { _workerResetEvent = new AutoResetEvent(false); _workItems = new ConcurrentQueue<WorkItem>(); _thread = new Thread(DoWork); _thread.Start(this); } private void DoWork(object obj) { SynchronizationContext.SetSynchronizationContext(obj as SynchronizationContext); while (true) { WorkItem workItem; while (_workItems.TryDequeue(out workItem)) workItem.Execute(); //Note: race condition here _workerResetEvent.Reset(); _workerResetEvent.WaitOne(); } } public override void Send(SendOrPostCallback d, object state) { if (Thread.CurrentThread == _thread) d(state); else { using (var resetEvent = new AutoResetEvent(false)) { var wiExecutionInfo = new WorkItemExecutionInfo(); _workItems.Enqueue(new SynchronousWorkItem(d, state, resetEvent, ref wiExecutionInfo)); _workerResetEvent.Set(); resetEvent.WaitOne(); if (wiExecutionInfo.HasException) throw wiExecutionInfo.Exception; } } } public override void Post(SendOrPostCallback d, object state) { _workItems.Enqueue(new AsynchronousWorkItem(d, state)); _workerResetEvent.Set(); } public void Dispose() { _workerResetEvent.Dispose(); _thread.Abort(); } private class WorkItemExecutionInfo { public bool HasException => Exception != null; public Exception Exception { get; set; } } private abstract class WorkItem { protected readonly SendOrPostCallback SendOrPostCallback; protected readonly object State; protected WorkItem(SendOrPostCallback sendOrPostCallback, object state) { SendOrPostCallback = sendOrPostCallback; State = state; } public abstract void Execute(); } private class SynchronousWorkItem : WorkItem { private readonly AutoResetEvent _syncObject; private readonly WorkItemExecutionInfo _workItemExecutionInfo; public SynchronousWorkItem(SendOrPostCallback sendOrPostCallback, object state, AutoResetEvent resetEvent, ref WorkItemExecutionInfo workItemExecutionInfo) : base(sendOrPostCallback, state) { if (workItemExecutionInfo == null) throw new NullReferenceException(nameof(workItemExecutionInfo)); _syncObject = resetEvent; _workItemExecutionInfo = workItemExecutionInfo; } public override void Execute() { try { SendOrPostCallback(State); } catch (Exception ex) { _workItemExecutionInfo.Exception = ex; } _syncObject.Set(); } } private class AsynchronousWorkItem : WorkItem { public AsynchronousWorkItem(SendOrPostCallback sendOrPostCallback, object state) : base(sendOrPostCallback, state) { } public override void Execute() { SendOrPostCallback(State); } } }
      
      







ここでは、便宜上、必要な方法でコード(デリゲート)を実行するWorkItemクラスを紹介します。 それから、さらに2つのSynchronousWorkItemとAsynchronousWorkItemを継承します。名前によって、それらの違いが明らかです。 実装では、唯一の違いは、同期バージョンでは、期待値(AutoResetEvent)と例外のスローが実装されていることです。これらは後で元のストリームでスローされます。 KeyValuePair <SendOrPostCallback、object>をWorkItemに変更できるようになりました。まあ、単純なキューを競合するキューに変更します。 また、Sendメソッドに現在のスレッドのチェックを追加し、突然 "ours"になった場合は、ここでデリゲートを実行します。



もう一度確認してください。



 static void Main(string[] args) { var syncContext = new CustomSynchronizationContext(); try { syncContext.Send(o => { throw new Exception("TestException"); }, null); } catch (Exception ex) { Console.WriteLine(ex.Message); } }
      
      





これで、例外が正常に処理されました。 さて、作成した同期コンテキストを使用してスレッドに関する記事で言及された最初のコードサンプルを実行します。



 static void Main(string[] args) { var syncContext = new CustomSynchronizationContext(); syncContext.Post(TestAsyncMethod, null); } async static void TestAsyncMethod(object obj) { Console.WriteLine(Thread.CurrentThread.ManagedThreadId); await Task.Run(() => Console.WriteLine(Thread.CurrentThread.ManagedThreadId)); Console.WriteLine(Thread.CurrentThread.ManagedThreadId); }
      
      





私の結論:



9

10

9



All Articles