AppDomain await async Task prevent SerializationException

放肆的年华 提交于 2020-01-02 04:39:07

问题


I have a windows service, which loads assembly in another AppDomain at runtime. Then it executes them and finally unloads the AppDomain. The problem is the execute method from the plugins are async tasks and I get the SerializationException because Task does not inherit from MarshalByRefObject.

I wrapped the plugin in a proxy which inherits from MarshalByRefObject, but I dont know how to get rid of the SerializationException?

public interface IPlugin : IDisposable
{
    Guid GUID { get; }
    string Name { get; }
    string Description { get; }
    Task Execute(PluginPanel panel, string user);
}

The proxy:

[Serializable()]
public class PluginProxy : MarshalByRefObject, IPlugin
{
    private IPlugin m_Plugin;

    public bool Init(string file)
    {
        Assembly ass = Assembly.Load(AssemblyName.GetAssemblyName(file));
        if (ass == null || ass.GetTypes() == null || ass.GetTypes().Length == 0)
            return false;
        foreach (Type type in ass.GetTypes())
        {
            if (type.IsInterface || type.IsAbstract)
                continue;
            if (type.GetInterface(typeof(IPlugin).FullName) != null)
            {
                m_Plugin = (IPlugin)Activator.CreateInstance(type);
                return true;
            }
        }
        return false;
    }


    public Guid GUID { get { return m_Plugin.GUID; } }
    public string Name { get { return m_Plugin.Name; } }
    public string Description { get { return m_Plugin.Description; } }
    // I debugged and found out the error happens AFTER m_Plugin.Execute
    // so the method runs well, but the return back to the pProxy.Execute is throwing the SerializationException       
    public async Task Execute(PluginPanel panel, string user) { await m_Plugin.Execute(panel, user); }
}

And the Method which loads the Assembly and gets the SerializationException:

        AppDomainSetup setup = new AppDomainSetup();
        // some setup stuff

        AppDomain dom = AppDomain.CreateDomain(Guid.NewGuid().ToString(), null, setup);
        PluginProxy pProxy = (PluginProxy)dom.CreateInstanceFromAndUnwrap(Assembly.GetExecutingAssembly().CodeBase, typeof(PluginProxy).FullName);
        pProxy.Init(app.Apppath);
        // I await the task later in code, because the user can cancel the execution
        try { tExe = pProxy.Execute(panel, user.Username); }
           catch (System.Runtime.Serialization.SerializationException e)
           {
               // runs always in this catch, even if no Exception from the plugin was thrown
           }
           catch (Exception e) { AddToErrorLog(panel.PanelName, e); }
           finally
           {
               pProxy.Dispose();
               AppDomain.Unload(dom);
           }

Maybe my whole concept of loading Plugins is wrong?


回答1:


Thanks to Hamlet Hakobyan and the post from Stephen Toub, I think I was able to solve the problem.

I replaced the line from the caller

 try { tExe = pProxy.Execute(panel, user.Username); }

with

 tExe = DoWorkInOtherDomain(pProxy, panel, user.Username);

and the method DoWorkInOtherDomain:

private Task DoWorkInOtherDomain(PluginProxy pProxy, PluginPanel panel, string user)
    {
        var ch = new MarshaledResultSetter<string>();
        pProxy.Execute(panel, user, ch);
        return ch.Task;
    }

and finally the proxy class:

 Task.Run(() =>
        {
            try
            {
                m_Plugin.Execute(panel, user).Wait();
            }
            catch (AggregateException e)
            {
                if (e.InnerExceptions != null)
                    foreach (Exception ein in e.InnerExceptions)
                        AddToErrorLog(panel.PanelName, ein);
            }
            catch (Exception e) { AddToErrorLog(panel.PanelName, e); }
            finally { ch.SetResult(AppDomain.CurrentDomain.FriendlyName); }
        });

I need to call Wait() in

m_Plugin.Execute(panel, user).Wait();

it catches the Exceptions from the plugin so everything is doing fine. The Wait() call should only blocking the Task.Run and not the other Tasks.

Can anyone tell me if this is a good solution or should I change something? I dont need a result so I just do:

 ch.SetResult(AppDomain.CurrentDomain.FriendlyName);

because I dont know how I should do it without a result.



来源:https://stackoverflow.com/questions/31555702/appdomain-await-async-task-prevent-serializationexception

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!