Ответ 1
Домены приложений и междоменное взаимодействие очень тонкие, поэтому нужно убедиться, что он действительно понимает, как все работает, прежде чем что-либо делать... Ммм... Скажем, "нестандартные": -)
Прежде всего, ваш метод создания потока фактически выполняется в вашем домене "по умолчанию" (сюрприз-сюрприз!). Зачем? Простой: метод, который вы передаете в AppDomain.DoCallBack
, определяется на объекте AppDomainDelegateWrapper
, и этот объект существует в вашем домене по умолчанию, поэтому в этом случае его метод запускается. MSDN не говорит об этой маленькой "функции", но достаточно легко проверить: просто установите точку останова в AppDomainDelegateWrapper.Invoke
.
Итак, в основном, вам нужно обойтись без объекта "обертка". Используйте статический метод для аргумента DoCallBack.
Но как вы передаете свой аргумент "func" в другой домен, чтобы ваш статический метод мог его поднять и выполнить?
Наиболее очевидным способом является использование AppDomain.SetData
, или вы можете сворачивать свои собственные, но независимо от того, как именно вы это делаете, возникает другая проблема: если "func" - это нестатический метод, то объект, который он должен быть каким-то образом передан в другой appdomain. Он может передаваться либо по значению (в то время как он копируется, по полю), либо по ссылке (создание междоменной ссылки объекта со всей красотой Remoting). Чтобы сделать прежний, класс должен быть отмечен атрибутом [Serializable]
. Чтобы сделать последнее, он должен унаследовать от MarshalByRefObject
. Если класс не является ничем, исключение будет выбрано при попытке передать объект другому домену. Имейте в виду, однако, что передача по ссылке в значительной степени убивает всю идею, потому что ваш метод все равно будет вызываться в том же домене, в котором объект существует, то есть по умолчанию.
Завершая приведенный выше параграф, вы остаетесь двумя вариантами: либо передайте метод, определенный в классе, помеченном атрибутом [Serializable]
(и помните, что объект будет скопирован), либо передайте статический метод. Я подозреваю, что для ваших целей вам понадобится первая.
И на всякий случай, когда это ускользнет от вашего внимания, я хотел бы указать, что ваш второй перегрузка RunInAppDomain
(тот, который принимает Action
), передает метод, определенный в классе, который не помечен [Serializable]
. Не видите ли там какой-либо класс? Вам не нужно: с анонимными делегатами, содержащими связанные переменные, компилятор создаст один для вас. И так получилось, что компилятор не потрудился отметить этот автогенерированный класс [Serializable]
. Несчастливо, но это жизнь: -)
Сказав все это (много слов, не так ли?:-), и если ваш обет не пропускать какие-либо нестатические и не-t24 методы, вот ваши новые методы RunInAppDomain
:
/// <summary>
/// Executes a method in a separate AppDomain. This should serve as a simple replacement
/// of running code in a separate process via a console app.
/// </summary>
public static T RunInAppDomain<T>(Func<T> func)
{
AppDomain domain = AppDomain.CreateDomain("Delegate Executor " + func.GetHashCode(), null,
new AppDomainSetup { ApplicationBase = Environment.CurrentDirectory });
try
{
domain.SetData("toInvoke", func);
domain.DoCallBack(() =>
{
var f = AppDomain.CurrentDomain.GetData("toInvoke") as Func<T>;
AppDomain.CurrentDomain.SetData("result", f());
});
return (T)domain.GetData("result");
}
finally
{
AppDomain.Unload(domain);
}
}
[Serializable]
private class ActionDelegateWrapper
{
public Action Func;
public int Invoke()
{
Func();
return 0;
}
}
public static void RunInAppDomain(Action func)
{
RunInAppDomain<int>( new ActionDelegateWrapper { Func = func }.Invoke );
}
Если вы все еще со мной, я ценю: -)
Теперь, потратив столько времени на фиксацию этого механизма, я расскажу вам, что в любом случае это было бесцельно.
Дело в том, что AppDomains не поможет вам в ваших целях. Они только заботятся об управляемых объектах, а неуправляемый код может протекать и разбивать все, что он хочет. Неуправляемый код даже не знает, что есть такие вещи, как appdomains. Он знает только о процессах.
Итак, в конце концов, ваш лучший вариант остается вашим текущим решением: просто создайте другой процесс и будьте счастливы. И я бы согласился с предыдущими ответами, вам не нужно писать другое консольное приложение для каждого случая. Просто передайте полное имя статического метода и приложите консольное приложение к своей сборке, загрузите свой тип и вызовите метод. Вы можете фактически упаковать его довольно аккуратно так же, как и с AppDomains. Вы можете создать метод, называемый "RunInAnotherProcess", который будет рассматривать аргумент, получить полное имя типа и имя метода из него (при условии, что метод статический) и создать консольное приложение, которое сделает все остальное.