【问题标题】:AppDomain await async Task prevent SerializationExceptionAppDomain 等待异步任务防止 SerializationException
【发布时间】:2015-07-22 06:42:58
【问题描述】:

我有一个 Windows 服务,它在运行时将程序集加载到另一个 AppDomain 中。然后它执行它们并最终卸载 AppDomain。问题是插件中的执行方法是异步任务,我得到了 SerializationException,因为 Task 没有从 MarshalByRefObject 继承。

我将插件封装在一个继承自 MarshalByRefObject 的代理中,但我不知道如何摆脱 SerializationException?

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

代理:

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

以及加载程序集并获取 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);
           }

也许我加载插件的整个概念是错误的?

【问题讨论】:

  • 您的Execute 也返回Task。在你的包装器中同步等待Execute
  • so 而不是 await m_Plugin.Execute(panel, user);我应该这样做:m_Plugin.Execute(panel, user);?或者这个:m_Plugin.Execute(panel, user).Wait();?但是 Wait() 会阻塞整个线程?
  • 好的,看看 Stephen Toub post。你应该为你的情况做些小改动。
  • 谢谢,我查看了帖子,但没有异步?他创建了一个返回 Task 的静态 Task 方法。但是我需要一个异步方法,所以我可以等待它并且我不能在异步任务方法中返回一些东西。我对整个异步事情很陌生,所以也许我只是忽略了一些东西。
  • 您可以等待DoWorkInOtherDomain返回的Task。

标签: c# asynchronous task appdomain


【解决方案1】:

感谢 Hamlet Hakobyan 和 Stephen Toub 的帖子,我想我能够解决问题。

我替换了调用者的线路

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

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

以及DoWorkInOtherDomain方法:

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

最后是代理类:

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

我需要在

中调用Wait()
m_Plugin.Execute(panel, user).Wait();

它从插件中捕获异常,因此一切正常。 Wait() 调用应该只阻止 Task.Run 而不是其他任务。

谁能告诉我这是一个好的解决方案还是我应该改变什么?我不需要结果,所以我只需要:

 ch.SetResult(AppDomain.CurrentDomain.FriendlyName);

因为没有结果我不知道该怎么做。

【讨论】:

  • 我现在唯一的问题是我无法从插件中捕获异常。即使我扔一个,m_Plugin.Execute(panel, user); 也不会抓住它。如果Execute 方法抛出异常并且'PluginProxy' 中的m_Plugin.Execute(panel, user);await m_Plugin.Execute(panel, user); 都没有捕获Exception,它会去哪里?如果抛出未处理的异常,应用程序不应该崩溃吗?
猜你喜欢
  • 1970-01-01
  • 2014-03-18
  • 2014-09-06
  • 2013-02-10
  • 2017-07-22
  • 1970-01-01
  • 1970-01-01
  • 2019-04-08
  • 2014-10-01
相关资源
最近更新 更多