[〜#〜] update [〜#〜]この質問の目的は、Task.Run()
およびデッドロックに関する簡単な答えを得ることです。私は非同期と同期を混ぜないことの理論的理由をよく理解しており、それらを心に留めています。私は他の人から新しいことを学ぶ以上のものではありません。できるときはいつでもそうするよう努めています。技術的な答えだけが必要な場合があります...
非同期メソッドを呼び出す必要があるDispose()
メソッドがあります。私のコードの95%は非同期であるため、リファクタリングは最良の選択ではありません。フレームワークでサポートされているIAsyncDisposable
(他の機能の中でも)が理想的ですが、まだありません。そのため、当面は、デッドロックなしで同期メソッドから非同期メソッドを呼び出すための信頼できる方法を見つける必要があります。
notを使用してConfigureAwait(false)
を使用することをお勧めします。これにより、呼び出し元が同期している場合に、呼び出し先が特定の動作をするようにコード全体に責任が分散されるためです。それは逸脱した盗聴者であるため、同期メソッドで何かをすることを好む。
Task.Run()
は常に非同期メソッドでもスレッドプールをスケジュールするという別の質問でStephen Clearyのコメントを読んだ後、私は考えさせられました。
ASP.NETの.NET 4.5または現在のスレッド/同じスレッドにタスクをスケジュールする他の同期コンテキスト(非同期メソッドがある場合):
_private async Task MyAsyncMethod()
{
...
}
_
非同期メソッドをスレッドプールにキューイングするため、同期メソッドから呼び出したいのですが、Task.Run()
をWait()
と一緒に使用してデッドロックを回避できますか?
_private void MySynchronousMethodLikeDisposeForExample()
{
// MyAsyncMethod will get queued to the thread pool
// so it shouldn't deadlock with the Wait() ??
Task.Run((Func<Task>)MyAsyncMethod).Wait();
}
_
あなたはあなたの質問に伴うリスクを理解しているようですので、私は講義をスキップします。
実際の質問に答えるには:はい、Task.Run
を使用して、その作業をThreadPool
のないSynchronizationContext
スレッドにオフロードすることができます。したがって、デッドロックの本当のリスクはありません。
ただし、SCがないという理由だけで別のスレッドを使用するのは、ハッキングのようなものであり、ThreadPool
で実行する作業のスケジューリングにはコストがかかるため、コストがかかる可能性があります。
IMOのより優れた明確なソリューションは、SCを一時的に削除して、SynchronizationContext.SetSynchronizationContext
を使用し、後で復元することです。これはIDisposable
に簡単にカプセル化できるため、using
スコープで使用できます。
public static class NoSynchronizationContextScope
{
public static Disposable Enter()
{
var context = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(null);
return new Disposable(context);
}
public struct Disposable : IDisposable
{
private readonly SynchronizationContext _synchronizationContext;
public Disposable(SynchronizationContext synchronizationContext)
{
_synchronizationContext = synchronizationContext;
}
public void Dispose() =>
SynchronizationContext.SetSynchronizationContext(_synchronizationContext);
}
}
使用法:
private void MySynchronousMethodLikeDisposeForExample()
{
using (NoSynchronizationContextScope.Enter())
{
MyAsyncMethod().Wait();
}
}
これは、非同期メソッドを同期的に呼び出す必要があり、スレッドがUIスレッドである場合にデッドロックを回避する私の方法です。
public static T GetResultSafe<T>(this Task<T> task)
{
if (SynchronizationContext.Current == null)
return task.Result;
if (task.IsCompleted)
return task.Result;
var tcs = new TaskCompletionSource<T>();
task.ContinueWith(t =>
{
var ex = t.Exception;
if (ex != null)
tcs.SetException(ex);
else
tcs.SetResult(t.Result);
}, TaskScheduler.Default);
return tcs.Task.Result;
}
このコードは、あなたが質問で強調した正確な理由でデッドロックしません-コードは常に同期コンテキストなしで実行され(スレッドプールを使用するため)、Wait
は単にスレッドtill/ifメソッドが戻ることをブロックします。
小さなカスタム同期コンテキストを使用すると、同期関数は、デッドロックを作成することなく、非同期関数の完了を待つことができます。元のスレッドは保持されるため、syncメソッドは、非同期関数の呼び出しの前後に同じスレッドを使用します。 WinFormsアプリの小さな例を次に示します。
Imports System.Threading
Imports System.Runtime.CompilerServices
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
SyncMethod()
End Sub
' waiting inside Sync method for finishing async method
Public Sub SyncMethod()
Dim sc As New SC
sc.WaitForTask(AsyncMethod())
sc.Release()
End Sub
Public Async Function AsyncMethod() As Task(Of Boolean)
Await Task.Delay(1000)
Return True
End Function
End Class
Public Class SC
Inherits SynchronizationContext
Dim OldContext As SynchronizationContext
Dim ContextThread As Thread
Sub New()
OldContext = SynchronizationContext.Current
ContextThread = Thread.CurrentThread
SynchronizationContext.SetSynchronizationContext(Me)
End Sub
Dim DataAcquired As New Object
Dim WorkWaitingCount As Long = 0
Dim ExtProc As SendOrPostCallback
Dim ExtProcArg As Object
<MethodImpl(MethodImplOptions.Synchronized)>
Public Overrides Sub Post(d As SendOrPostCallback, state As Object)
Interlocked.Increment(WorkWaitingCount)
Monitor.Enter(DataAcquired)
ExtProc = d
ExtProcArg = state
AwakeThread()
Monitor.Wait(DataAcquired)
Monitor.Exit(DataAcquired)
End Sub
Dim ThreadSleep As Long = 0
Private Sub AwakeThread()
If Interlocked.Read(ThreadSleep) > 0 Then ContextThread.Resume()
End Sub
Public Sub WaitForTask(Tsk As Task)
Dim aw = Tsk.GetAwaiter
If aw.IsCompleted Then Exit Sub
While Interlocked.Read(WorkWaitingCount) > 0 Or aw.IsCompleted = False
If Interlocked.Read(WorkWaitingCount) = 0 Then
Interlocked.Increment(ThreadSleep)
ContextThread.Suspend()
Interlocked.Decrement(ThreadSleep)
Else
Interlocked.Decrement(WorkWaitingCount)
Monitor.Enter(DataAcquired)
Dim Proc = ExtProc
Dim ProcArg = ExtProcArg
Monitor.Pulse(DataAcquired)
Monitor.Exit(DataAcquired)
Proc(ProcArg)
End If
End While
End Sub
Public Sub Release()
SynchronizationContext.SetSynchronizationContext(OldContext)
End Sub
End Class
must同期メソッドからasyncメソッドを絶対に呼び出す場合は、非同期メソッド呼び出し内でConfigureAwait(false)
を使用して、同期コンテキストのキャプチャを回避してください。
これは保持されるはずですが、せいぜい不安定です。リファクタリングを考えることをお勧めします。代わりに。