Loosely-ordered concurrency with loops?

2019-08-28 03:16发布

问题:

I have the following code that is meant to fetch data from a REST service (the Get(i) calls), then populate a matrix (not shown; this happens within addLabels()) with their relationships.

All of the Get() calls can run in parallel to each other, but they must all be finished before anything enters the second loop (where, once again, the calls can run in parallel to each other). The addLabel() calls depend on the work from the Get() calls to be complete.

** To anyone stumbling across this post, this code is the solution:**

private async void GetTypeButton_Click(object sender, RoutedEventArgs e)
{
    await PokeType.InitTypes(); // initializes relationships in the matrix

    var table = PokeType.EffectivenessMatrix;

    // pretty-printing the table
    // ...
    // ...
}

private static bool initialized = false;

public static async Task InitTypes()
{
    if (initialized) return;

    // await blocks until first batch is finished
    await Task.WhenAll(Enumerable.Range(1, NUM_TYPES /* inclusive */).Select(i => Get(i)));

    // doesn't need to be parallelized because it's quick work.
    foreach(PokeType type in cachedTypes.Values)
    {
        JObject data = type.GetJsonFromCache();
        addLabels(type, (JArray)data["super_effective"], Effectiveness.SuperEffectiveAgainst);
        addLabels(type, (JArray)data["ineffective"], Effectiveness.NotVeryEffectiveAgainst);
        addLabels(type, (JArray)data["no_effect"], Effectiveness.UselessAgainst);
    }

    initialized = true;
}

public static async Task<PokeType> Get(int id);

As the code is currently written, the InitTypes() method attempts to enter both loops simultaneously; the cachedTypes dictionary is empty because the first loop hasn't finished populating it yet, so it never runs and no relationships are constructed.

How can I properly structure this function? Thanks!

回答1:

Parallel and async-await don't go together well. Your async lambda expression is actually async void since Parallel.For excpects an Action<int>, which means that Parallel.For can't wait for that operation to complete.

If you're trying to call Get(i) multiple times concurrently and wait for them to complete before moving on you need to use Task.WhenAll:

await Task.WhenAll(Enumerable.Range(1, NUM_TYPES).Select(() => Get(i)))