AppDomain.Unload throws ThreadAbortException exception

1

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

  • Create new AppDomain
  • Create an instance of ObjFromOtherAppDomain within the new AppDomain
  • invoke Do method passing MarshalableCompletionSource in order later to know when async Do method is completed.
  • Once 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?

c#
multithreading
async-await
appdomain
asked on Stack Overflow Aug 12, 2019 by tchelidze • edited Aug 21, 2019 by tchelidze

1 Answer

1

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

  1. Main -> objFromOtherAppDomain.Do(source); - Thread1 starts execution of Do method

  2. Do -> await Task.Delay(1000); - Thread1 returns back to Main method and awaits source.Task, Thread2 continues execution of Do method.

  3. Do -> source.SetResult(true); - Thread2 sets the result to MarshalableCompletionSource.Task and continues execution of Main method (Without finishing up the Do method)

  4. 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

answered on Stack Overflow Aug 21, 2019 by tchelidze

User contributions licensed under CC BY-SA 3.0