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
.