Работа с несериализуемыми необработанными исключениями из дочернего домена приложения - PullRequest
6 голосов
/ 28 марта 2011

Мы используем System.AddIn для загрузки надстроек в отдельные дочерние домены приложений, и мы выгружаем надстройку AppDomain, если у нее есть необработанное исключение.

Поскольку стандартное поведение .NET начиная с версии 2.0 заключалось в том, чтобы завершить весь процесс, если у какого-либо домена приложений есть необработанное исключение, мы делаем это с помощью параметра «legacyUnhandledExceptionPolicy» в нашем файле App.config, а затем вручную срываем процесс если необработанное исключение было в основном домене приложений, или выгрузка соответствующего домена приложений, если оно было в надстройке.

Все это прекрасно работает, за исключением одной небольшой проблемы: необработанные исключения всегда всплывают от дочерних доменов приложений к основному, и если они не сериализуемы, они не могут успешно пересечь границу домена приложений.

Вместо этого мы получаем исключение SerializationException, которое отображается как исключение UnhandledException в главном домене приложений, в результате чего наше приложение разрывается.

Я могу придумать несколько возможных решений этой проблемы:

  • Мы не можем разорвать процесс для необработанных исключений SerializationException (yuck).

  • Мы могли бы остановить распространение исключений из дочернего домена приложений на основной домен приложений.

  • Мы могли бы заменить несериализуемые исключения сериализуемыми, возможно, используя суррогаты сериализации и связыватели сериализации. [Изменить: см. Конец, почему это невозможно с помощью суррогатов]

Однако первый довольно ужасен, и мне не удалось выяснить, как сделать любой из других вариантов с удаленным удалением через AppDomain.

Может кто-нибудь дать совет? Любая помощь приветствуется.

Для воспроизведения создайте консольное приложение со следующим App.config:

<?xml version="1.0"?>
<configuration>
  <runtime>
    <legacyUnhandledExceptionPolicy enabled="true"/>
  </runtime>
</configuration>

и следующий код:

class Program
{
    private class NonSerializableException : Exception
    {
    }

    static void Main(string[] args)
    {
        AppDomain.CurrentDomain.UnhandledException += MainDomain_UnhandledException;

        AppDomain childAppDomain = AppDomain.CreateDomain("Child");
        childAppDomain.UnhandledException += ChildAppDomain_UnhandledException;

        childAppDomain.DoCallBack(
            () => new Thread(delegate() { throw new NonSerializableException(); }).Start());

        Console.ReadLine();
    }

    static void MainDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        Console.WriteLine("Main AppDomain Unhandled Exception: " + e.ExceptionObject);
        Console.WriteLine();
    }

    static void ChildAppDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        Console.WriteLine("Child AppDomain Unhandled Exception: " + e.ExceptionObject);
        Console.WriteLine();
    }
}



РЕДАКТИРОВАТЬ: я использовал рефлектор, чтобы увидеть, есть ли какой-нибудь способ получить доступ к BinaryFormatter, используемому в удаленном удаленном взаимодействии между доменами приложений, но в конечном итоге это код внутри класса CrossAppDomainSerializer:

internal static void SerializeObject(object obj, MemoryStream stm)
{
    BinaryFormatter formatter = new BinaryFormatter();
    RemotingSurrogateSelector selector = new RemotingSurrogateSelector();
    formatter.SurrogateSelector = selector;
    formatter.Context = new StreamingContext(StreamingContextStates.CrossAppDomain);
    formatter.Serialize(stm, obj, null, false);
}

Таким образом, он создает средство форматирования локально в методе, и нет никакого способа присоединить мой собственный суррогат ... Я думаю, что это делает любые дополнительные усилия в этом направлении бесполезными.

1 Ответ

2 голосов
/ 29 марта 2011

Я бы обработал исключения в удаленном домене приложения. Сначала я должен создать новую сборку с кодом обработки исключений, а затем загрузить его в дочерний домен приложения.

Следующий код должен дать некоторые идеи.

class Program {
    private class NonSerializableException : Exception { }

    static void Main(string[] args) {
        var childAppDomain = AppDomain.CreateDomain("Child");
        Console.WriteLine("Created child AppDomain #{0}.", childAppDomain.Id);

        // I did not create a new assembly for the helper class because I am lazy :)
        var helperAssemblyLocation = typeof(AppDomainHelper).Assembly.Location;
        var helper = (AppDomainHelper)childAppDomain.CreateInstanceFromAndUnwrap(
                helperAssemblyLocation, typeof(AppDomainHelper).FullName);
        helper.Initialize(UnloadHelper.Instance);

        childAppDomain.DoCallBack(
            () => new Thread(delegate() { throw new NonSerializableException(); }).Start());

        Console.ReadLine();
    }

    private sealed class UnloadHelper : MarshalByRefObject, IAppDomainUnloader {
        public static readonly UnloadHelper Instance = new UnloadHelper();

        private UnloadHelper() { }

        public override object InitializeLifetimeService() {
            return null;
        }

        public void RequestUnload(int id) {
            // Add application domain identified by id into unload queue.
            Console.WriteLine("AppDomain #{0} requests unload.", id);
        }
    }
}

// These two types could be in another helper assembly

public interface IAppDomainUnloader {
    void RequestUnload(int id);
}

public sealed class AppDomainHelper : MarshalByRefObject {
    public void Initialize(IAppDomainUnloader unloader) {
        AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
                unloader.RequestUnload(AppDomain.CurrentDomain.Id);
    }
}
...