Я столкнулся с небольшими трудностями при использовании класса threadpool и массива ManualResetEvents. Ниже приведен простой пример того, что я делаю. Проблема в том, что в методе DoWork я получаю пустые ссылки на объект resetEvent [param as int].

Не могу понять, что делаю не так.

(изменить: получил рабочий блок кода)

private static volatile ManualResetEvent[] resetEvents = new ManualResetEvent[NumThreads];
public void UpdateServerData()
{
   for (int i = 0; i < NumThreads ; i++)
        {
            resetEvents[i] = new ManualResetEvent(false);
            ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), (object) i);

        }
  WaitHandle.WaitAll(resetEvents);
}
private void DoWork(object param)
{
//do some random work
resetEvents[(int)param].Set();
}

РЕДАКТИРОВАТЬ: я попытался вставить System.Threading.Thread.MemoryBarrier (); после каждого .Set (), однако я все еще получаю исключение с нулевой ссылкой.

1
Setheron 20 Авг 2009 в 19:07
Я сделал несколько правок, чтобы блок кода заработал. Если кто-то видел более ранние версии, извините за беспорядок, который это был.
 – 
Setheron
20 Авг 2009 в 19:12
Я также пытался заблокировать каждый вызов .Set (), потому что это должно вызывать непостоянное чтение / запись объекта, однако это тоже не работает. Очень неприятно.
 – 
Setheron
20 Авг 2009 в 19:59
С какой стати вы создаете барьер памяти после Set()? Вам нужно увидеть обновленный элемент массива перед вызовом для него Set()!
 – 
Anton Tykhyy
21 Авг 2009 в 19:03
В качестве примечания, я надеюсь, что вы используете RegisteredWaitHandle.Unregister после использования потока, как рекомендовано msdn, в отличие от приведенного здесь кода.
 – 
cregox
27 Сен 2012 в 17:13

3 ответа

Лучший ответ

В значительной степени я обнаружил, что проблема была в

for (int i = 0; i < NumThreads ; i++)    
{        
resetEvents[i] = new ManualResetEvent(false);        
ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), resetEvents[i]);    
}

Вместо объявления нового события ManualResetEvent я просто вызвал Reset (). Проблема, казалось, заключалась в том, что, хотя я бы использовал MemoryBarrier или блокировки, физическая память еще не обновлялась, поэтому она указывала на null.

0
Setheron 20 Авг 2009 в 22:03

Вы не можете использовать ключевое слово as для преобразования в int (поскольку int не является ссылочным типом). Вместо этого используйте (int)param:

private void DoWork(object param)
{
    //do some random work
    resetEvents[(int)param].Set();
}

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

public void UpdateServerData()
{
    for (int i = 0; i < NumThreads ; i++)
    {
        resetEvents[i] = new ManualResetEvent(false);
        ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), resetEvents[i]);
    }
    WaitHandle.WaitAll(resetEvents);
}
private void DoWork(object param)
{
    //do some random work
    (param as ManualResetEvent).Set();
}

Таким образом, рабочий метод ничего не знает о том, как дескриптор ожидания управляется извне; и он также не может по ошибке достичь дескрипторов ожидания для других потоков.

2
Fredrik Mörk 20 Авг 2009 в 19:24
Я просто быстро написал простой пример. Я не хотел, чтобы это было на 100% синтаксически правильным, но я исправлю пример
 – 
Setheron
20 Авг 2009 в 19:14
Не думаю, что вы хотели использовать параметр в качестве индексатора .... должен иметь возможность просто делать (параметр как ManualResetEvent) .Set ();
 – 
CSharpAtl
20 Авг 2009 в 19:24
@CSharpAtl: да, я это заметил. Мой код умер смертью копирования / вставки, я думаю; o)
 – 
Fredrik Mörk
20 Авг 2009 в 19:26

volatile ManualResetEvent[] не означает, что доступ к элементам массива следует изменчивой семантике. Только доступ к переменной, содержащей ссылку на массив, будет изменчивым. Попробуйте вставить барьер памяти после назначения элемента массива или используйте Thread.VolatileWrite для их установки, например

Thread.VolatileWrite (ref resetEvents[i], new ManualResetEvent (false)) ;
1
Anton Tykhyy 20 Авг 2009 в 20:47
Ой. Я думал, что присвоение volatile массиву заставляет члены наследовать ключевое слово. Я не знаком с двумя другими упомянутыми вами способами (барьер памяти и Interlocked.Exchange)
 – 
Setheron
20 Авг 2009 в 19:28
На самом деле Interlocked.Xxx здесь не очень хорошая идея, поскольку вам не нужно читать и обновлять свои данные.
 – 
Anton Tykhyy
20 Авг 2009 в 20:48