0

A LINQ expression that uses a lambda expression without the async keyword can specify an index. For example:

var list = FileList.Select((file, index) => new { Index=index, Filename=file });

I am trying to obtain the index while using the async keyword. For example:

await Task.WhenAll(urlList.Select(async url =>
{
   byte[] urlContents = await GetWebPageAsync(url);
   lock (Locker) { webResults.Add(URLContents); }
}));

The reason I would like to obtain the index is so that the web page contents can be stored in an array instead of using a lock statement or searching within the collection for the index.

When I try to add an index to the above query, it gives compiler errors.

Is there a way to specify the index in the above query, or can another LINQ expression be used (other than Select) that supports using an async lambda expression with an index?

Community
  • 1
  • 1
Bob Bryan
  • 3,687
  • 1
  • 32
  • 45
  • You shouldn't be performing side effects in your projections. It's bad enough in normal code, but when dealing with multithreaded code it's all the more difficult to work with. The lambda performing the projection should return the results of the operation, not add them to a list. You can then use the results of the returned sequence. – Servy Mar 14 '17 at 17:25

1 Answers1

0

What I just realized is that the code will always be single threaded when it comes back from the await - and it will always be on the same thread from which it was called. In this case, it is a user interface thread. So, no lock statement is required.

To verify this, I put in some calls to get the thread id:

string ThreadID = Thread.CurrentThread.ManagedThreadId.ToString();
await Task.WhenAll(urlList.Select(async url =>
{
   string TaskThreadID = Thread.CurrentThread.ManagedThreadId.ToString();
   byte[] URLContents = await GetWebPageAsync(url);
   string TaskThreadID2 = Thread.CurrentThread.ManagedThreadId.ToString();
   lock (locker) { webResults.Add(URLContents); }
     }));

They are all the same value. I had a misconception about how the Task.WhenAll method worked.

Even though in this case I don't need the index, there will be times when it would be helpful. If anyone can show how the index can be gracefully obtained, I will accept the answer.

Bob Bryan
  • 3,687
  • 1
  • 32
  • 45
  • For the graceful solution, just use two select statements `FileList.Select((file, index) => new { Index=index, Filename=file }).Select(async data => { foo = data.Index; \\... });` – Scott Chamberlain Mar 14 '17 at 18:56
  • @ScottChamberlain I will try it out. – Bob Bryan Mar 14 '17 at 20:15
  • @ScottChamberlain That looks like it will work, but the code creates a new object for each element in the collection. I don't know if that is any better than doing a sequential search for each url within the collection (when the collection is small) to find the index or using a Dictionary for this when it is larger. Microsoft should add an overload method that can be used with async to obtain the index within the collection - just like it does with regular LINQ. – Bob Bryan Mar 14 '17 at 20:45