How to run looping task untill other task is finished

I found many questions addressing how to sequence tasks and waiting until all tasks finish, but with this topic, I found only 1 question from 2016 with no answers.

I'm processing a large text file in my project and I want to indicate that this process is running with the text being displayed with changing number of dots after the "Processing" text. I got to the point, where the intended looping task is working until a long working task finishes and the proper field in the VM is updated, but I can't make looping task to be delayed so dots are changing in the way it's seen.

In other words - the same functionality as when a loader is displayed while data are being retrieved from the HTTP request.

public void SetRawTextFromAbsPath(string path)
{
    if (!File.Exists(path))
    {
        return;
    }

    var rawText = "Processing";
    bool IsReadingFileFinished = false;

    Task<string> getRawTextFromAbsPath = Task.Run(() => {
        var result = FileProcessingServices.GetRawFileText(path);
        IsReadingFileFinished = true;
        return result;
        });

    Task updateProgressText = Task.Run(async () =>
    {
        while (!IsReadingFileFinished)
        {
            rawText = await Task.Run(() => ProcessingTextChange(rawText));
            SelectedFileRaw = rawText;
        }
    });

    Task.WaitAll(getRawTextFromAbsPath, updateProgressText);

    SelectedFileRaw = completeRawText.Result;
}

public string ProcessingTextChange(string text)
{
    Task.Delay(100);
    var dotsCount = text.Count<char>(ch => ch == '.');

    return dotsCount < 6 ? text + "." : text.Replace(".", "");
}

After learning from all the answers, I come up with this solution:

private const string PROGRESS = "Progress";
private const int PROGRESS_DELAY = 200;

        public async void RunProgressTextUpdate()
        {
            var cts = new CancellationTokenSource();

            if (!IsRunning)
            {
                UpdateProgressTextTask(cts.Token);
                string longTaskText = await Task.Run(() => LongTask(cts));
                await Task.Delay(PROGRESS_DELAY);
                ProgressText = longTaskText;
            }                  
        }

        private void UpdateProgressTextTask(CancellationToken token)
        {
            Task.Run(async () =>
            {
                ProgressText = PROGRESS;
                while (!token.IsCancellationRequested)
                {
                    await Task.Delay(PROGRESS_DELAY);
                    var dotsCount = ProgressText.Count<char>(ch => ch == '.');

                    ProgressText = dotsCount < 6 ? ProgressText + "." : ProgressText.Replace(".", "");
                }
            });

        }

        private string LongTask(CancellationTokenSource cts)
        {
            var result = Task.Run(async () =>
            {
                await Task.Delay(5000);
                cts.Cancel();
                return "Long task finished.";
            });

            return result.Result;
        }

Upvotes: 0

Views: 1513

Answers (1)

VRoxa
VRoxa

Reputation: 1051

Every way of creating Task and running them is overloaded to expect a CancellationToken. CancellationTokens are, unsurprinsignly, structs that allows us to cancel Tasks.

Having this two methods

public void DelayedWork()
{
    Task.Run(async () =>
    {
        // Simulate some async work
        await Task.Delay(1000);
    });
}

public void LoopingUntilDelayedWorkFinishes()
{
    Task.Run(() =>
    {
        int i = 0;
        // We keep looping until the Token is not cancelled
        while (true) // May be?
        {
            Console.WriteLine($"{++i} iteration ...");
        }
    });
}

We want LoopingUntilDelayedWorkFinishes to stop looping when DelayedWork finishes (well, naming was quite obvious).

We can provide a CancellationToken to our LoopingUntilDelayedWorkFinishes method. So it will keep looping until it is cancelled.

public void LoopingUntilDelayedWorkFinishes(CancellationToken token)
{
    Task.Run(() =>
    {
        int i = 0;
        // We keep looping until the Token is not cancelled
        while (!token.IsCancellationRequested)
        {
            Console.WriteLine($"{++i} iteration ...");
        }
    }, token); // This is the overload expecting the Token
}

Okay, working. We can control this CancellationToken by extracting from a CancellationTokenSource, which controls its CancellationToken.

var cts = new CancellationTokenSource();
p.LoopingUntilDelayedWorkFinishes(cts.Token);

And now, we need our DelayedWork to cancel the token when it finishes.

public void DelayedWork(CancellationTokenSource cts)
{
    Task.Run(async () =>
    {
        // Simulate some async work
        await Task.Delay(1000);

        // Once it is done, we cancel.
        cts.Cancel();
    });
}

That is how we could call the methods.

var cts = new CancellationTokenSource();
p.DelayedWork(cts);
p.LoopingUntilDelayedWorkFinishes(cts.Token);

The call order between DelayedWork and LoopingUntilDelayedWorkFinishes is not that important (in that case).

Maybe LoopingUntilDelayedWorkFinishes can return a Task and the await for it later on, I don't know. I just depends on our needs.

There are tons of ways to achieve this. The environment arround Task is so bast and the API is quite confusing sometimes.
Here's how you could do it. Maybe some smart use of async/await syntax would improve the solution I gave. But, here's the main idea.

Hope it helps.

Upvotes: 3

Related Questions