La mia app deve caricare plug-in in domini app separati e quindi eseguire codice all'interno di essi in modo asincrono. Ho scritto un codice per avvolgere Task
nei tipi marshallable:Deadlock durante la combinazione di dominio remoto e attività
static class RemoteTask
{
public static async Task<T> ClientComplete<T>(RemoteTask<T> remoteTask,
CancellationToken cancellationToken)
{
T result;
using (cancellationToken.Register(remoteTask.Cancel))
{
RemoteTaskCompletionSource<T> tcs = new RemoteTaskCompletionSource<T>();
remoteTask.Complete(tcs);
result = await tcs.Task;
}
await Task.Yield(); // HACK!!
return result;
}
public static RemoteTask<T> ServerStart<T>(Func<CancellationToken, Task<T>> func)
{
return new RemoteTask<T>(func);
}
}
class RemoteTask<T> : MarshalByRefObject
{
readonly CancellationTokenSource cts = new CancellationTokenSource();
readonly Task<T> task;
internal RemoteTask(Func<CancellationToken, Task<T>> starter)
{
this.task = starter(cts.Token);
}
internal void Complete(RemoteTaskCompletionSource<T> tcs)
{
task.ContinueWith(t =>
{
if (t.IsFaulted)
{
tcs.TrySetException(t.Exception);
}
else if (t.IsCanceled)
{
tcs.TrySetCancelled();
}
else
{
tcs.TrySetResult(t.Result);
}
}, TaskContinuationOptions.ExecuteSynchronously);
}
internal void Cancel()
{
cts.Cancel();
}
}
class RemoteTaskCompletionSource<T> : MarshalByRefObject
{
readonly TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();
public bool TrySetResult(T result) { return tcs.TrySetResult(result); }
public bool TrySetCancelled() { return tcs.TrySetCanceled(); }
public bool TrySetException(Exception ex) { return tcs.TrySetException(ex); }
public Task<T> Task
{
get
{
return tcs.Task;
}
}
}
E 'usato come:
sealed class ControllerAppDomain
{
PluginAppDomain plugin;
public Task<int> SomethingAsync()
{
return RemoteTask.ClientComplete(plugin.SomethingAsync(), CancellationToken.None);
}
}
sealed class PluginAppDomain : MarshalByRefObject
{
public RemoteTask<int> SomethingAsync()
{
return RemoteTask.ServerStart(async cts =>
{
cts.ThrowIfCancellationRequested();
return 1;
});
}
}
Ma ho incontrato un intoppo. Se guardi su ClientComplete
, c'è uno Task.Yield()
che ho inserito. Se commento questa riga, ClientComplete
non tornerà mai più. Qualche idea?
Controlla i risultati di ricerca per "C# async deadlock ConfigureAwait" come http://stackoverflow.com/questions/13489065/best-practice-to-call-configureawait-for-all-server-side-code come penso sarebbe una soluzione. –
Non riesco a ripeterlo. 'ControllerAppDomain.SomethingAsync' non si blocca mai per me, sia che lo blocchi o usi' await', sia in un contesto di pool di thread o in un contesto a thread singolo. Sei sicuro che il codice sopra duplica il problema? –
@StephenCleary Ho appena provato il codice su un'altra macchina e non riesco a riprodurlo neanche lì. Interessante. –