2015-07-22 25 views
5

Ho un servizio di Windows, che carica assembly in un altro AppDomain in fase di esecuzione. Quindi li esegue e infine scarica AppDomain. Il problema è che il metodo di esecuzione dei plugin è un'attività asincrona e ottengo SerializationException perché Task non eredita da MarshalByRefObject.AppDomain attende asincronia Attività impedisce SerializationException

Ho avvolto il plugin in un proxy che eredita da MarshalByRefObject, ma non so come sbarazzarmi di SerializationException?

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

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

e il metodo che carica l'Assemblea e ottiene il 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); 
      } 

Forse tutto il mio concetto di caricamento di plugin è sbagliato?

+0

Il tuo 'Execute' restituisce anche il' Task'. Nel tuo wrapper attendi "Esegui" in modo sincrono. –

+0

così invece di attendere m_Plugin.Execute (pannello, utente); Dovrei fare questo: m_Plugin.Execute (pannello, utente) ;? O questo: m_Plugin.Execute (pannello, utente) .Wait() ;? Ma l'Wait() bloccherebbe l'intero thread? – Tony

+0

OK, guarda Stephen Toub [post] (https://social.msdn.microsoft.com/Forums/vstudio/en-US/28277f25-5f5d-4b7c-bf1f-402937fc9f31/tasks-across-appdomain). Dovresti apportare piccole modifiche al tuo caso. –

risposta

2

Grazie a Hamlet Hakobyan e al post di Stephen Toub, penso di essere stato in grado di risolvere il problema.

ho sostituito la linea dal chiamante

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

con

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

e il metodo DoWorkInOtherDomain:

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

ed infine la classe proxy:

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

ho bisogno di chiamare Wait() in

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

cattura le eccezioni dal plugin quindi tutto sta facendo bene. La chiamata Wait() dovrebbe solo bloccare Task.Run e non le altre attività.

Qualcuno può dirmi se questa è una buona soluzione o devo cambiare qualcosa? Non ho bisogno di un risultato così io faccio solo:

ch.SetResult(AppDomain.CurrentDomain.FriendlyName); 

perché io non so come devo fare senza un risultato.

+0

L'unico problema che ho ora è che non riesco a catturare Eccezioni dai plugin. Anche se ne lancio uno, m_Plugin.Esegui (pannello, utente); 'non lo cattura. Se il metodo 'Execute' lancia un'eccezione e nè' m_Plugin.Execute (pannello, utente); 'nemmeno' attendi m_Plugin.Execute (pannello, utente); 'in 'PluginProxy' cattura 'Eccezione', dove sta andando? Non dovrebbe andare in crash l'applicazione se viene lanciata un'eccezione non gestita? – Tony