Moja aplikacja musi ładować wtyczki do oddzielnych domen aplikacji, a następnie wykonać asynchronicznie kod wewnątrz nich. Pisałem jakiś kod zawinąć Task
w marshallable typów:Zakleszczenie podczas łączenia funkcji zdalnej domeny aplikacji i zadań
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;
}
}
}
Jest używany jak:
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;
});
}
}
Ale ja napotkasz niepowodzeniem. Jeśli spojrzysz w ClientComplete
, wstawiłem Task.Yield()
. Jeśli skomentuję tę linię, ClientComplete
nigdy nie wróci. Jakieś pomysły?
Sprawdź wyniki wyszukiwania dla "C# async deadlock ConfigureAwait", np. Http://stackoverflow.com/questions/13489065/best-practice-to-call-configureawait- for-all-server-side-code, jak myślę byłoby rozwiązaniem. –
Nie mogę tego zaimponować. 'ControllerAppDomain.SomethingAsync' nigdy nie zawiesza się dla mnie, bez względu na to, czy go blokuję, czy też używam opcji" czekaj ", czy to w kontekście puli wątków, czy w kontekście pojedynczego wątku. Czy na pewno powyższy kod duplikuje problem? –
@StephenCleary Właśnie wypróbowałem kod na innym komputerze i nie mogę go tam odtworzyć. Ciekawy. –