Starcev Misha
Starcev Misha

Reputation: 58

Async C# Tasks.WhenAny

I need to run Task and Timer, and if the timer ends earlier, then run another task and continue to wait for the answer from both the first and second tasks and return result of first completed. And I need to restart the timer. I think to do something like that

foreach (var uri in Addresses)
{
    var webRequest = CreateRequest(uri + "?query=" + query);            
    resultTasks.Add(ProcessRequestAsync(webRequest));
}
Task<string> firstFinishedTask = await Task.WhenAny(resultTasks);



<-- if timer end early -->
resultTasks.Add(<--newTask-->);
Task<string> firstFinishedTask = await Task.WhenAny(resultTasks);

But at this point I catch an error, because the list already has a running task.

What you can recomend?

In other words: I have list of tasks

List<Task> tasksList = new List<Task>();
<-- some initialization of this list-->

Then I start Task.WhenAny(tasksList); How I can add another Tasks to WhenAny?

Upvotes: 0

Views: 2567

Answers (2)

thanatorr
thanatorr

Reputation: 103

If you must continue to wait for all tasks then try await Task.WhenAll(Task);.

Also, it seems messy to order things in this way, possibly use a recursive function to send off each of your tasks and have your main thread in a while loop with a diagnostics timer on it. Then finally await all at the end after the loop.

readonly List<Task<T>> _taskList = new List<Task<T>>();

void fireThreads()
{
    var timer = Stopwatch.StartNew();
    var taskLoop = Task.Factory.StartNew(()=> DO STUFF);
    _taskList.Add(taskLoop);
    while(await Task.WhenAll(TaskList))
    {
        if (timer.ElapsedMilliseconds / 1000 > TargetTime)
            fireThreads();
    }
}

Definitely not the best code example but something like this might work.

Upvotes: 0

Gabor
Gabor

Reputation: 3246

I think I found a possible solution for you. But instead of Timer I used Task.Delay(...).

Here is the code of the sample WinForms application which contains a Button btnStart, a ProgressBar progressBar and a RichTextBox log on its main form.

CreateNextTask() will create a new Task if needed.

GetWaitTask() plays the role of a timer.

It seemed working.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace TaskAndTimer
{
    public partial class MainForm : Form
    {
        private int[] taskConfigs = { 1200, 1300, 1100, 800 };
        private Queue<int> queue;

        public MainForm()
        {
            InitializeComponent();
        }

        private async void btnStart_Click(object sender, EventArgs e)
        {
            queue = new Queue<int>(taskConfigs);

            Task<int> firstTask;
            Task<int>[] tasks = new Task<int>[] { };
            int result = -1;

            progressBar.Style = ProgressBarStyle.Marquee;

            do
            {
                log.AppendText($"Next round...{Environment.NewLine}");
                log.AppendText($"-------------{Environment.NewLine}");

                tasks =
                    tasks
                        .Concat(new[] { GetWaitTask(), CreateNextTask() })
                        .ToArray();

                LogTasks("Current tasks:", tasks);

                firstTask = await Task.WhenAny(tasks);

                LogTasks("First task:", firstTask);

                tasks = tasks.Except(new[] { firstTask }).ToArray();

                LogTasks("Remaining tasks:", tasks);

                result = await firstTask;

                log.AppendText($"-------------{Environment.NewLine}");
                log.AppendText(Environment.NewLine);
            }
            while (result == -1 && queue.Any());

            log.AppendText($"result = [{result}]\r\n");

            progressBar.Style = ProgressBarStyle.Blocks;
        }

        private Task<int> GetWaitTask()
        {
            return Task.Run(async () => { await Task.Delay(1000); return -1; });
        }

        private Task<int> CreateNextTask()
        {
            if (queue.Any())
            {
                int data = queue.Dequeue();
                return Task.Run(async () => { await Task.Delay(data); return data; });
            }

            return Task.FromResult(-1);
        }

        private void LogTasks(string message, params Task<int>[] tasks)
        {
            log.AppendText(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fffffff"));
            log.AppendText($"> {message}");

            foreach (var task in tasks)
            {
                log.AppendText($" [{task.Id}];");
            }

            log.AppendText(Environment.NewLine);
        }
    }
}

And the result was this:

Next round...
-------------
2018-02-23 19:30:31.2273143> Current tasks: [2]; [4];
2018-02-23 19:30:32.2300773> First task: [2];
2018-02-23 19:30:32.2330774> Remaining tasks: [4];
-------------

Next round...
-------------
2018-02-23 19:30:32.2375774> Current tasks: [4]; [14]; [16];
2018-02-23 19:30:32.4392640> First task: [4];
2018-02-23 19:30:32.4452575> Remaining tasks: [14]; [16];
-------------

result = [1200]

Upvotes: 2

Related Questions