2012-01-25 6 views
-1

Хорошо, так что у меня здесь проблема. Вот петля.C# foreach with Action.BeginInvoke

lock (ClientLocker) 
{ 
    Trace.WriteLine("#WriteAll: " + sm.Header); 
    foreach (Client c in Clients) 
    { 
     if (c.LoggedIn) 
     { 
      Trace.WriteLine("#TryWriteTo[" + c.Id + "](" + sm.Header + ")"); 
      LazyAsync.Invoke(() => c.WriteMessage(sm)); 
     } 
    } 
} 

Вот LazyAsync

public static class LazyAsync 
{ 
    public static void Invoke(Action a) 
    { 
     a.BeginInvoke(a.EndInvoke, null); 
    } 
} 

Каждый Client содержит socket, так что я не могу с трудом Clone это. Проблема заключается в том, что когда я делаю Invoke до c.WriteMessage, так как исполнение задерживается, оно обычно не срабатывает по первой паре в списке, и иногда на самом деле будет срабатывать только целая куча на самом последнем элементе.

Я знаю, что это связано с тем, что c является ссылкой, которая изменяется до того, как Invoke действительно вызван, но есть ли способ избежать этого?

Выполнение общего цикла for(int i=0 etc, похоже, не устраняет эту проблему.

У кого-нибудь есть идеи о том, как я могу это исправить?

Помните, что не может CloneClient.

+3

Кто-то спрашивает об этом почти каждый день. См. Http://stackoverflow.com/questions/8898925/is-there-a-reason-for-cs-reuse-of-the-variable-in-a-foreach/8899347#8899347 для некоторых ссылок и обсуждения вопроса , –

+0

Я сделал поиск, но ничего не нашел. Не так, как я не пытался. –

+0

Действительно; это трудно найти. Именно поэтому этот вопрос задают снова почти каждый день. Если вы на самом деле не получите предупреждение «доступ к модифицированному закрытию» от resharper, нет никакой причины, по которой вы знаете, какие ключевые слова для поиска. –

ответ

5

Скопируйте c на локальную переменную, как это:

lock (ClientLocker) 
{ 
    Trace.WriteLine("#WriteAll: " + sm.Header); 
    foreach (Client c in Clients) 
    { 
     if (c.LoggedIn) 
     { 
      Client localC = c; 
      Trace.WriteLine("#TryWriteTo[" + c.Id + "](" + sm.Header + ")"); 
      LazyAsync.Invoke(() => localC.WriteMessage(sm)); 
     } 
    } 
} 

ли веб-поиска: «Доступ к модифицированному закрытия», если вы хотите получить больше информации.

+0

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

1

Ваше подозрение справедливо: переменная c захватывается выражением лямбда, но не оценивается дольше.

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

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

lock (ClientLocker) 
{ 
    Trace.WriteLine("#WriteAll: " + sm.Header); 
    foreach (Client c in Clients) 
    { 
     if (c.LoggedIn) 
     { 
      Trace.WriteLine("#TryWriteTo[" + c.Id + "](" + sm.Header + ")"); 

      Client copyOfC = c; 
      LazyAsync.Invoke(() => copyOfC.WriteMessage(sm)); 
     } 
    } 
} 

Вот несколько связанных StackOverflow сообщений:

+0

Я ценю ссылки, будет приятно читать еще кое-что. Одна из тех серых линий, где вы не уверены, что вы ссылаетесь: p –

1

установка Try с локальным переменным и вызова LazyAsync.Invoke на том, что, чтобы избежать гр переподчиняемого к петле Еогеаспа перед Invoke происходит. Когда LazyAsync.Invoke делает c.WriteMessage, он звонит WriteMessage на то, что с происходит с теперь указывают на то, что, не это было, когда LazyAsync.Invoke (() => c.WriteMessage (см)) оценивали

foreach (Client c in Clients) 
{ 
    if (c.LoggedIn) 
    { 
     Trace.WriteLine("#TryWriteTo[" + c.Id + "](" + sm.Header + ")"); 

     Client client = c; 
     LazyAsync.Invoke(() => client.WriteMessage(sm)); 
    } 
} 
+0

Спасибо за ответ. –