Как передать CancellationToken через границу AppDomain?

У меня есть объект команды, выполняющий работу на основе запроса из очереди запросов. Эта конкретная команда выполнит свою работу в дочернем домене. Часть выполнения своей работы в дочернем appdomain включает блокировку операции ConcurrentQueue (например, "Добавить или принять" ). Мне нужно иметь возможность распространять сигнал прерывания через очередь запросов, через дочернюю область приложения и пробуждать рабочие потоки в ней.

Поэтому, я думаю, мне нужно передать CancellationToken через границу AppDomain.

Я попытался создать класс, который наследует от MarshalByRefObject:

protected class InterAppDomainAbort : MarshalByRefObject, IAbortControl
    {
        public InterAppDomainAbort(CancellationToken t)
        {
            Token = t;
        }

        [SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.Infrastructure)]
        public override object InitializeLifetimeService()
        {
            return null;
        }

        public CancellationToken Token
        {
            get;
            private set;
        }

    };

и передавая это как аргумент рабочей функции:

// cts is an instance variable which can be triggered by another thread in parent appdomain
cts = new CancellationTokenSource();
InterAppDomainAbort abortFlag = new InterAppDomainAbort(cts.Token);
objectInRemoteAppDomain = childDomain.CreateInstanceAndUnwrap(...);

// this call will block for a long while the work is being performed.
objectInRemoteAppDomain.DoWork(abortFlag);

Но я все еще получаю исключение, когда objectInRemoteAppDomain пытается получить доступ к свойству geten getter:

System.Runtime.Serialization.SerializationException: Type 'System.Threading.CancellationToken' in Assembly 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' is not marked as serializable.

Мой вопрос: как я могу распространять сигнал прерывания/отмены в приложениях и пробуждать потоки, которые могут быть заблокированы в структурах данных .NET concurrency (где аргументы CancellationToken поддерживаются).

Ответ 1

Прошло некоторое время с тех пор, как я просмотрел любые файлы Cross-AppDomain, поэтому могут возникнуть проблемы с этим кодом, которые я не понял, но, похоже, он выполняет эту работу. Основная проблема заключается в том, что нет способа передать CancellationToken [Source] из одного AppDomain в другой. Поэтому я создаю два источника, с первичной настройкой, чтобы отменить вторичное, если это необходимо.

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

Стандартные предупреждения о минимальной проверке ошибок, реализации Dispose и т.д.

// I split this into a separate interface simply to make the boundary between
// canceller and cancellee explicit, similar to CancellationTokenSource itself.
public interface ITokenSource
{
    CancellationToken Token { get; }
}

public class InterAppDomainCancellable: MarshalByRefObject,
                                        ITokenSource,
                                        IDisposable
{
    public InterAppDomainCancellable()
    {
        cts = new CancellationTokenSource();
    }

    public void Cancel() { cts.Cancel(); }

    // Explicitly implemented to make it less tempting to call Token
    // from the wrong side of the boundary.
    CancellationToken ITokenSource.Token { get { return cts.Token; } }

    public void Dispose() { cts.Dispose(); }

    private readonly CancellationTokenSource cts;
}

// ...

// Crucial difference here is that the remotable cancellation source
// also lives in the other domain.
interAppDomainCancellable = childDomain.CreateInstanceAndUnwrap(...);

var primaryCts = new CancellationTokenSource();
// Cancel the secondary when the primary is cancelled.
primaryCts.Token.Register(() => interAppDomainCancellable.Cancel());

objectInRemoteAppDomain = childDomain.CreateInstanceAndUnwrap(...);
// DoWork expects an instance of ITokenSource.
// It can access Token because they're all in the same domain together.
objectInRemoteAppDomain.DoWork(interAppDomainCancellable);

Ответ 2

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

Создайте поля tokenSource и токенов и инициализируйте их в своем конструкторе.

_cancellationTokenSource = new CancellationTokenSource();
_token = _cancellationTokenSource.Token;

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

var currDomain = AppDomain.CurrentDomain;
            currDomain.DomainUnload += currDomain_DomainUnload;
            currDomain.UnhandledException += currDomain_UnhandledException;

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

void currDomain_DomainUnload(object sender, EventArgs e)
    {
        _log.Debug(FormatLogMessage(_identity, "Domain unloading Event!"));
        _cancellationTokenSource.Cancel();
        _logPlayer.Dispose();
    }

 void currDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        _log.Error(string.Format("***APP Domain UHE*** Error:{0}", e.ExceptionObject);
        _cancellationTokenSource.Cancel();
        _logPlayer.Dispose();
    }