Having
public class ObjFromOtherAppDomain : MarshalByRefObject
{
public async void Do(MarshalableCompletionSource<bool> source)
{
await Task.Delay(1000);
source.SetResult(true);
}
}
public class MarshalableCompletionSource<T> : MarshalByRefObject
{
private readonly TaskCompletionSource<T> tsc = new TaskCompletionSource<T>();
public void SetResult(T result) => tsc.SetResult(result);
public void SetException(Exception[] exception) => tsc.SetException(exception);
public void SetCanceled() => tsc.SetCanceled();
public Task<T> Task => tsc.Task;
}
Doing
AppDomainObjFromOtherAppDomain within the new AppDomainDo method passing MarshalableCompletionSource in order later to know when async Do method is completed.Do method is completed, trying to Unload the AppDomain public static async Task Main()
{
var otherDomain = AppDomain.CreateDomain("other domain");
var objFromOtherAppDomain = (ObjFromOtherAppDomain)otherDomain
.CreateInstanceAndUnwrap(
typeof(ObjFromOtherAppDomain).Assembly.FullName,
typeof(ObjFromOtherAppDomain).FullName);
var source = new MarshalableCompletionSource<bool>();
objFromOtherAppDomain.Do(source);
await source.Task;
//await Task.Yield();
AppDomain.Unload(otherDomain);
}
Getting
System.Threading.ThreadAbortException: 'Thread has aborted. (Exception from HRESULT: 0x80131530) exception
Fix
Uncomment await Task.Yield(); line and Unload works well.
Short analysis
Main thread enters Do method and on the line await Task.Delay(1000), Main thread returns back to Main method, while new background thread gets pulled from ThreadPool (it's happening in otherDomain) and continues execution of continuation, in this case, rest of the Do method.
After that, the same (background) thread starts executing rest of the Main method (the part after await source.Task)
At that moment background thread hits AppDomain.Unload(otherDomain), it should be done in otherDomain and happily unload it, but, apparently, it's not.
If i'll yield (release, set free) that background thread by await Task.Yield(), new Background thread comes into play and does AppDomain.Unload happily.
Why is that?
With the help of my colleague, I found out the issue.
Short
When the result is set to TaskCompletionSource, continuation attached to TaskCompletionSource.Task runs on the same thread that called TaskCompletionSource.SetResult, causing Do method not to be completed at the time AppDomain.Unload is called.
Detailed
Main -> objFromOtherAppDomain.Do(source); - Thread1 starts execution of Do method
Do -> await Task.Delay(1000); - Thread1 returns back to Main method and awaits source.Task, Thread2 continues execution of Do method.
Do -> source.SetResult(true); - Thread2 sets the result to MarshalableCompletionSource.Task and continues execution of Main method (Without finishing up the Do method)
Main -> AppDomain.Unload(otherDomain); - Thread2 tries to unload the AppDomain, but since Do method isn't yet finished, unload fails.
On the other hand, if we do await Task.Yield(), it will cause Thread2 to return from Main method to Do method and finish it up, after that AppDomain can be unloaded.
Related
Calling TaskCompletionSource.SetResult in a non blocking manner
User contributions licensed under CC BY-SA 3.0