5

I need to start a "number" of tasks (variable but less than 10) not in parallel, and wait for them all to finish, getting from each the result. I'm getting the result from each of them, saving in a list and then using it in the end.

Here's my code, and it's working but I think there gotta be a cleaner way to do that.

CAUSING THE NUMBER OF TASKS

List<String> Arguments = new List<String> { "AA", "BB", "CC" }; 

List<String> ResultList = new List<String>();  

//**AT LEAST I'VE GOT ONE**

Task<String> Tasks = Task<String>.Factory.StartNew(() =>
{
    return DoSomething(Arguments[0]);
});

ResultList.Add(Tasks.Result);

for (Int32 i = 1; i < Arguments.Count; i++)
{
    ResultList.Add(Tasks.ContinueWith<String>(Result =>
    {
        return DoSomething(Arguments[i]);

    }).Result);
}

//**DO I NEED THIS?? It's working even without!!**
//Tasks.Wait();

for (Int32 i = 0; i < ResultList.Count; i++)
{
    textBox1.AppendText(ResultList[i] + Environment.NewLine + Environment.NewLine);
}
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
AngelBlueSky
  • 575
  • 4
  • 10
  • 3
    Why do you need more than one task? Why do you need a task at all when all you are doing is **synchronously** waiting for it to complete. – Alex Apr 17 '15 at 08:18
  • you can use async await – Orifjon Apr 17 '15 at 08:20
  • `Tasks.Result` won't compile. – Sriram Sakthivel Apr 17 '15 at 08:20
  • If you don't want them in parallel it's not multithreading. If you just want non-ui-blocking, that's asynchronous. You don't need continuation because you're not passing the arguments from one task to the next. – MrFox Apr 17 '15 at 08:22
  • Tasks.Result won't compile -> Yes it works (have you tried it?) – AngelBlueSky Apr 17 '15 at 09:01
  • Thanks I would have done surely in a different way but I'm calling a web api (Not mine) that doesn't allow me multiple call in parallet, that's why I need to wait for each task to finish. – AngelBlueSky Apr 17 '15 at 09:03
  • @user3103198 If you're calling WebAPI, you don't need threadpool threads at all. HTTP calls are asynchronous by nature. – Yuval Itzchakov Apr 17 '15 at 09:04

4 Answers4

5

I think this is what you are attempting to do : ie start a whole load of parallel tasks and wait for them all to complete before proceeding

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace UnitTestProject2
{
    class Class4
    {
        public void run()
        {
            List<String> Arguments = new List<String> { "AA", "BB", "CC" };
            List<Task<String>> tasks = new List<Task<string>>();

            foreach (string arg in Arguments)
            {
                    tasks.Add(
                        this.DoSomething(arg)
                        .ContinueWith(t => this.DoSomething(t.Result))
                        .Unwrap<string>()
                    );
            }

            Task.WaitAll(tasks.ToArray());

            foreach(Task<string> t in tasks)
            {
                textBox1 += (t.Result + Environment.NewLine + Environment.NewLine);
            }

        }

        public async Task<string> DoSomething(string arg)
        {
            return arg;
        }

        public string textBox1;
    }
}
Ewan
  • 1,261
  • 1
  • 14
  • 25
  • `.Result` can raise an `AggregateException` – VMAtm Apr 17 '15 at 13:08
  • you mean in the lambda? I must admit I normaly would continuewith a method and pass in the task so you can handle non completion but msdn has example with the result lambda and it is more consise for examples. also means you can use the same method twice in the case – Ewan Apr 17 '15 at 13:24
1

You do not need the Wait() call. Documentation for Task<T>.Result states:

Accessing the property's get accessor blocks the calling thread until the asynchronous operation is complete; it is equivalent to calling the Wait method.

Sami Kuhmonen
  • 30,146
  • 9
  • 61
  • 74
0

you can use async await

public async List<String> SomeMethod() {
   List<String> Arguments = new List<String> { "AA", "BB", "CC" }; 
   List<String> ResultList = new List<String>();  
   foreach(var argument in Arguments) 
   {
     var result = await DoSomething(argument);
     ResultList.Add(result);
   }
   return ResultList;
}
Orifjon
  • 915
  • 8
  • 25
0

Your entire code, as of now, runs synchronously. You're creating a Task then actively blocking on it, using Task.Result.

If you truly understand what parallelism is about, and you really need to execute as many tasks as you have arguments in your list, then you can offload all of them and then asynchronously wait (this is the key, being asynchronous) as they complete:

var arguments = new[] { "A", "B", "C" };
var tasks = arguments.Select(argument => Task.Run(() => DoSomething(argument))).ToList();

while (tasks.Count > 0)
{
    var finishedTask = await Task.WhenAny(tasks);
    textBox1.AppendText(string.Format("{0}{1}{1}", finishedTask.Result,
                                                   Environment.NewLine));

    tasks.Remove(finishedTask);
}

Edit: From your comments:

I'm calling a web api (Not mine) that doesn't allow me multiple call in parallel, that's why I need to wait for each task to finish

Then you don't need a thread for each argument at all. Two things:

  1. You can make the call entirely synchronous, as you have to wait for the completion of each call. Using threadpool threads for that is merely useless
  2. You can make asynchronous calls without needing to use threads at all. Look into HttpClient and its XXXAsync methods (such as GetAsync).
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321