Asynchronous method with foreach

2019-07-02 00:18发布

I have some async method

 public static Task<JObject> GetUser(NameValueCollection parameters)
        {
            return CallMethodApi("users.get", parameters, CallType.HTTPS);
        }

And I write method below

public static IEnumerable<JObject> GetUsers(IEnumerable<string> usersUids, Field fields)
{
    foreach(string uid in usersUids)
    {
        var parameters = new NameValueCollection
                             {
                                 {"uids", uid},
                                 {"fields", FieldsUtils.ConvertFieldsToString(fields)}
                             };
        yield return GetUser(parameters).Result;
    }
}

This method is asynchronous? How to write this using Parallel.ForEach?

2条回答
smile是对你的礼貌
2楼-- · 2019-07-02 01:11

Something kind of like this.

public static IEnumerable<JObject> GetUsers(IEnumerable<string> usersUids, Field fields)
{
    var results = new List<JObject>
    Parallel.ForEach(usersUids, uid => {
        var parameters = new NameValueCollection
                             {
                                 {"uids", uid},
                                 {"fields", FieldsUtils.ConvertFieldsToString(fields)}
                             };
        var user = GetUser(parameters).Result;
        lock(results)
            results.Add(user);
    });
    return results;
}

NOTE: The results won't be in the same order as you expect.

查看更多
手持菜刀,她持情操
3楼-- · 2019-07-02 01:13

Your method is not asynchronous. Assuming your GetUser method already starts an asynchronous task, Parallel.ForEach would use additional threads just to start off your tasks, which is probably not what you want.

Instead, what you probably want to do is to start all of the tasks and wait for them to finish:

public static IEnumerable<JObject> GetUsers(IEnumerable<string> usersUids, Field fields)
{
    var tasks = usersUids.Select(
        uid =>
        {
            var parameters = new NameValueCollection
            {
                {"uids", uid},
                {"fields", FieldsUtils.ConvertFieldsToString(fields)}
            };
            return GetUser(parameters);
        }
    ).ToArray();

    Task.WaitAll(tasks);

    var result = new JObject[tasks.Length];
    for (var i = 0; i < tasks.Length; ++i)
        result[i] = tasks[i].Result;

    return result;
}

If you also want to start them in parallel you can use PLINQ:

    var tasks = usersUids.AsParallel().AsOrdered().Select(
        uid =>
        {
            var parameters = new NameValueCollection
            {
                {"uids", uid},
                {"fields", FieldsUtils.ConvertFieldsToString(fields)}
            };
            return GetUser(parameters);
        }
    ).ToArray();

Both code snippets preserve relative ordering of uids and returned objects - result[0] corresponds to usersUids[0], etc.

查看更多
登录 后发表回答