c # foreach с Action.BeginInvoke - PullRequest
       26

c # foreach с Action.BeginInvoke

0 голосов
/ 26 января 2012

Хорошо, поэтому у меня есть небольшая проблема здесь.Вот цикл.

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кажется, не решает эту проблему.

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

Помните, не может Clone Client.

Ответы [ 3 ]

5 голосов
/ 26 января 2012

Скопируйте 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));
        }
    }
}

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

1 голос
/ 26 января 2012

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

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

        Client client = c;
        LazyAsync.Invoke(() => client.WriteMessage(sm));
    }
}
1 голос
/ 26 января 2012

Ваше подозрение верно: переменная 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:

...