С готовностью избавиться от ManualResetEvent - PullRequest
6 голосов
/ 07 июня 2011

У меня есть класс, который позволяет другим потокам ждать, пока он не завершит операцию, используя ManualResetEventSlim.(Операции, как правило, короткие)

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

Из соображений производительности я бы предпочел не использовать блокировку.

Является ли этот код поточно-ориентированным, иэто можно сделать быстрее?

volatile bool isCompleted;
volatile int waitingCount;
ManualResetEventSlim waiter = new ManualResetEventSlim();

//This method is called on any thread other than the one that calls OnCompleted
public void WaitForCompletion() {
    if (isCompleted)
        return;

    Interlocked.Increment(ref waitingCount);
    Thread.MemoryBarrier();
    if (!isCompleted)
        waiter.Wait();

    if (0 == Interlocked.Decrement(ref waitingCount)) {
        waiter.Dispose();
        waiter = null;
    }
    return;
}

//This method is called exactly once.
protected internal virtual void OnCompleted(string result) {
    Result = result;
    isCompleted = true;
    Thread.MemoryBarrier();
    if (waitingCount == 0) {
        waiter.Dispose();
        waiter = null;
    } else
        waiter.Set();
}

Ответы [ 2 ]

1 голос
/ 07 июня 2011

Самым большим, что я вижу в вашем коде, является установка waiter в null после вызова Dispose.У меня есть большое количество управляемых оболочек через неуправляемые интерфейсы, за которые я отвечаю, и когда я перешел на .Net 4.0, эта практика вернулась, чтобы укусить меня в некоторых сценариях многопоточности.предполагает, что он не является потокобезопасным, однако, глядя на его фактическую реализацию, нет ничего опасного в нескольких вызовах Dispose из нескольких потоков.Кроме того, реализации IDisposable должны быть очень терпимыми к нескольким вызовам (как указано в их руководстве по проектированию).

Одна идея, с которой я играл, изменила бы порядок OnCompleted немного, чтобы позволить читателю подписаться вскоре после его завершения:

//This method is called exactly once.
protected internal virtual void OnCompleted(string result) {
    Result = result;
    isCompleted = true;

    waiter.Set();
    Thread.MemoryBarrier();
    if (waitingCount == 0) {
        waiter.Dispose();
    }
}

1 голос
/ 07 июня 2011

Хуже того, существуют условия, при которых он вообще не избавляется от официанта.Если вы позвоните OnCompleted при waitingCount > 0, флаг isCompleted будет установлен на true, но официант не будет удален.Когда что-то вызывает WaitForCompletion, оно увидит, что isCompleted равно true, и немедленно выйдет.waiter.Dispose никогда не вызывается.

Почему бы не использовать что-то вроде SpinLock , которое использует ту же логику, что и ManualResetEventSlim?Если ваши ожидания, как правило, очень короткие, блокировка, вероятно, не будет оспорена, и это огромная победа.Если ожидание будет долгим, то ManualResetEventSlim все равно заплатит цену перехода ядра.

Вы так уверены, что использование блокировки будет непомерно дорого?Есть «знание», а затем измерение.,.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...