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
AppDomain
ObjFromOtherAppDomain
within the new AppDomain
Do
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