我有一个Windows服务,它在运行时在另一个AppDomain中加载程序集。然后它执行它们,最后卸载AppDomain。问题是来自插件的execute方法是异步任务,我得到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);
}
也许我加载插件的整个概念都错了?
答案 0 :(得分:2)
感谢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);
因为我不知道如何在没有结果的情况下这样做。