3

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?

6
  • 1
    async void is only meant for event handlers. It results in a fire-and-forget task that can't be awaited. That task may still be running when the application or ... application domain that launched it terminates. Use async Task if you want an asynchronous function that doesn't return anything. If you don't want the application to terminate before the task does, you'll have to wait for it Commented Aug 12, 2019 at 13:28
  • 1
    You could replicate this problem quite easily by using an async Task Main() and inside it call an async void method that waits eg for 10 seconds before trying to access a global object. Commented Aug 12, 2019 at 13:30
  • @PanagiotisKanavos correct, async void is there to support event handlers, but you can't really use Task here instead since Task isn't serializable and can't be used cross app-domain. That's why I'm passing MarshalableCompletionSource to Do method to Wait for its completion. Commented Aug 12, 2019 at 14:04
  • You can't not use async Task or async ValueTask. That's the invariant. What are you trying to do in the first place? There are probably other, better ways to do it. If you want two whatevers to communicate, you can use Channels, similar to Go's channels Commented Aug 12, 2019 at 14:08
  • @PanagiotisKanavos I want to run some task in new AppDomain and signal original appdomain whenever its done. I'll check out Channels, looks interesting. Thought, i still wanna understand why above exception is thrown. Commented Aug 13, 2019 at 9:27

1 Answer 1

4

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

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.