Home > database >  How to iterate dictionary in "await foreach"
How to iterate dictionary in "await foreach"

Time:08-21

Help with a small problem...

I have a method that returns a dictionary. I need to rewrite it in such a way that I can enumerate the result of this method using await foreach. Please help me, something is not working for me at all.

It's my method:

public IDictionary<long, long> TransformListInDictionary(IList<string> list)
{
    var result = new Dictionary<long, long>();
    foreach (var member in list)
    {
        var idx = member.IndexOf(':');

        var key = member.Substring(0, idx);
        var value = member.Substring(idx   1);

        result.Add(Convert.ToInt64(key), Convert.ToInt64(value));
    }
    return result;
}

To use a dictionary in an await foreach loop, this method requires a return value of IAsyncEnumerable<KeyValuePair<long,long>>.

Therefore, I have a question, how to rewrite the method published above is the return value.

Why do I need it.

I have some code which I am posting below. I'll try to describe my idea.

When the code enters the for loop, it does some work and instantiates the dictionary. Which is further processed in the foreach loop.

var logic = new AllLogic();

var variable = Convert.ToInt32(Console.ReadLine());
var randomSecundForPause = new Random();

for (int i = 0; i < variable; i  )
{
    var list = new List<string>();

    var dict = logic.TransformListInDictionary(list);

    //some code

    foreach (var item in dict)
    {
        try
        {
            //some code

            Thread.Sleep(randomSecundForPause.Next(100000, 150000));
        }
        catch (Exception e)
        {
            //some code

            Thread.Sleep(randomSecundForPause.Next(100000, 150000));
        }
    }

}

I would like this foreach loop to run in the background and the main code flow to go to a new iteration of the for loop.

As I understand it, I need to replace foreach with await foreach.

CodePudding user response:

What you want is probably an asynchronous iterator:

#pragma warning disable CS1998
public async IAsyncEnumerable<KeyValuePair<long, long>> ToAsyncEnumerable(
#pragma warning restore CS1998
    IList<string> list)
{
    foreach (var member in list)
    {
        var idx = member.IndexOf(':');

        var key = member.Substring(0, idx);
        var value = member.Substring(idx   1);

        yield return KeyValuePair.Create(Convert.ToInt64(key), Convert.ToInt64(value));
    }
}

The method must be async, it must have IAsyncEnumerable<X> as return type, and it must contain the yield contextual keyword.

The #pragma warning disable CS1998 is needed in order to suppress a warning about an async method that lacks the await keyword. Without it the program will still compile, but the C# compiler will emit a warning.

CodePudding user response:

I would like this foreach loop to run in the background and the main code flow to go to a new iteration of the for loop.

You don't need an await foreach. The for loop body would not continue to the next iteration once it hits an await of any kind. Instead, it would asynchronously wait for whatever it is to finish, and only when it does will the next iteration of the for loop start.

If you want to understand it better, try running this code:

for(int i = 0; i < 10; i  )
{
    Console.WriteLine($"{i} Started");
    await Task.Delay(2000);
    Console.WriteLine($"{i} Finished");
}

If you want the foreach body to run in the background, you need to wrap the body inside a Task.Run(...) call. This call would return a Task, so store the Task in a collection and await Task.WhenAll(...) afterwards.

Would something like this:

var tasks = new List<Task>(variable);
for (int i = 0; i < variable; i  )
{
    var list = new List<string>();

    var dict = logic.TransformListInDictionary(list);

    //some code
    var task = Task.Run(async () => 
    {
        foreach (var item in dict)
        {
            try
            {
                //some code

                await Task.Delay(randomSecundForPause.Next(100000, 150000)); // This is better because the thread is not blocked
            }
            catch (Exception e)
            {
                //some code

                await Task.Delay(randomSecundForPause.Next(100000, 150000));
            }
        }
    });
    tasks.Add(task);
}

await Task.WhenAll(tasks);

Notice that the Dictionary class is thread-safe for multiple readers, but not for multiple writers, so depending on what //some code does you might want to consider a ConcurrentDictionary.

  • Related