Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compiler.BuildMsi() & async/await #1

Closed
AlexMAS opened this issue Dec 2, 2016 · 2 comments
Closed

Compiler.BuildMsi() & async/await #1

AlexMAS opened this issue Dec 2, 2016 · 2 comments

Comments

@AlexMAS
Copy link

AlexMAS commented Dec 2, 2016

Hello!

I got an exception from Compiler.BuildMsi() method. I tried to find the reason and it took me a long time. Below I will explain my situation and my solution. I think it will help you to improve Wix#.

Compiler.BuildMsi() builds an installer in separated AppDomain. When AppDomain is created .NET passes CallContext of the parent domain to the created child domain. It means content of this CallContext will be serialized and then deserialized to be transferred it between domains. If CallContext contains non-serializable instance any invocation to the child AppDomain will return an exception. And that exception will not reflect the real problem.

Also we can't control content of the CallContext because we can use a third party code. So it would be great if we had "clean" CallContext before creating new AppDomain. To do this we can use ExecutionContext.SuppressFlow() method which suppresses the flow of the execution context i.e. after call SuppressFlow() new threads won't get parent CallContext and will have to use its own "clean" CallContext. Consequently you can create new AppDomain within new thread and don't worry about CallContext.

After work with child AppDomain you must restore the flow of the execution context using AsyncFlowControl.Undo() method. The Undo() method must be called in the same thread where SuppressFlow() method was invoked thus next code waits for completion a thread using Task.Wait() and doesn't use await.

Unfortunately only this workaround helped me and I can use Compiler in any CallContext (include async/await code). Probably there is more elegant solution but I think it must be part of your library. :)

// ...

await ExecuteInNewContext(() => Compiler.BuildMsi(...));

// ...

private static Task<T> ExecuteInNewContext<T>(Func<T> action)
{
    var taskResult = new TaskCompletionSource<T>();

    var asyncFlow = ExecutionContext.SuppressFlow();

    try
    {
        Task.Run(() =>
                 {
                     try
                     {
                         var result = action();

                         taskResult.SetResult(result);
                     }
                     catch (Exception exception)
                     {
                         taskResult.SetException(exception);
                     }
                 })
            .Wait();
    }
    finally
    {
        asyncFlow.Undo();
    }

    return taskResult.Task;
}

See original issue here.

UPDATE

I've created a little test project which demonstrates my issue and my suggestion.

https://github.com/AlexMAS/WixSharpAsyncTests.git

All test cases are in the file WixSharpAsyncTest.cs which represents NUnit test.

@oleg-shilo
Copy link
Owner

Thank you Alex for such a comprehensive test case.
Done: https://github.com/oleg-shilo/wixsharp/blob/master/Source/src/WixSharp.Samples/Wix%23%20Samples/AsyncBuild/setup.cs
Will be available with the next very release.

@AlexMAS
Copy link
Author

AlexMAS commented Dec 5, 2016

Your welcome! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants