AppDomain.Unload 抛出 ThreadAbortException 异常

AppDomain.Unload throws ThreadAbortException exception

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;
}

正在做

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);
}

得到

System.Threading.ThreadAbortException: 'Thread has aborted. (Exception from HRESULT: 0x80131530) exception

修复

取消注释 await Task.Yield(); 行,Unload 效果很好。

简析

主线程进入 Do 方法并在行 await Task.Delay(1000) 上,主线程 returns 返回 Main 方法,同时从 [=28 拉出新的后台线程=](它发生在 otherDomain 中)并继续执行 continuation,在这种情况下,Do 方法的其余部分。

之后,相同(后台)线程开始执行Main方法的其余部分(await source.Task之后的部分)

在那一刻后台线程命中 AppDomain.Unload(otherDomain),它应该在 otherDomain 中完成并愉快地卸载它,但显然不是。

如果我让出(释放,释放)那个背景线程await Task.Yield(),新的背景线程开始发挥作用并愉快地AppDomain.Unload .

这是为什么?

在同事的帮助下,我发现了问题。

当结果设置为 TaskCompletionSource 时,附加到 TaskCompletionSource.Task 的延续在调用 TaskCompletionSource.SetResult 相同 线程上运行,导致 Do 调用 AppDomain.Unload 时未完成的方法。

详细

  1. Main -> objFromOtherAppDomain.Do(source); - Thread1 开始执行 Do 方法

  2. Do -> await Task.Delay(1000); - Thread1 return 返回 Main 方法并等待 source.TaskThread2 继续执行 Do 方法。

  3. Do -> source.SetResult(true); - Thread2 将结果设置为 MarshalableCompletionSource.Task 并且 继续执行 Main 方法(没有完成Do方法)

  4. Main -> AppDomain.Unload(otherDomain); - Thread2 尝试卸载 AppDomain,但由于 Do 方法尚未完成完成,卸载失败。

另一方面,如果我们做await Task.Yield(),它会导致Thread2到return从Main方法到Do方法并完成它up,之后AppDomain就可以卸载了

相关

Calling TaskCompletionSource.SetResult in a non blocking manner