简体   繁体   中英

c# foreach with Action.BeginInvoke

Alright, so I'm having a bit of an issue here. Here is the loop.

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));
        }
    }
}

Here is LazyAsync

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

Each Client contains a socket , so I can't hardly Clone it. The problem is, when I do the Invoke to c.WriteMessage , since the execution is delayed, it usually won't fire on the first couple in the list, and will sometimes actually only fire a whole bunch on the very last item.

I know this has to do with c being a reference that changes before Invoke actually gets called, but is there a way to avoid this?

Doing a general for(int i=0 etc loop doesn't seem to fix this issue.

Anyone have any ideas on how I can fix this?

Remember, can't Clone Client .

Copy your c to local variable like this:

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));
        }
    }
}

Do a web search for: "Access to modified closure" if you want to get more information.

Your suspicion is right: the variable c is captured by the lambda expression, but not evaluated until later.

This flavor of error pops up whenever you make use of a loop variable within a lambda expression, since the loop variable is scoped outside the loop, and not with each iteration of the loop.

You can work around this by creating a new local variable in the foreach loop, assign c to it, and then pass that new local variable into the lambda expression:

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));
        }
    }
}

Here are a few related StackOverflow posts:

Try setting c to a local variable and calling LazyAsync.Invoke on that, to avoid c being reassigned to by the foreach loop before the invoke happens. When LazyAsync.Invoke does c.WriteMessage , it's calling WriteMessage on whatever c happens to now point to, not what it was when LazyAsync.Invoke(() => c.WriteMessage(sm)) was evaluated

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM