How to have mutliple threads await a single Task? - c#

I've read this: Is it ok to await the same task from multiple threads - is await thread safe? and I don't feel clear about the answer, so here's a specific use case.
I have a method that performs some async network I/O. Multiple threads can hit this method at once, and I dont wan't them all to invoke a network request, If a request is already in progress I want to block/await the 2nd+ threads, and have them all resume once the single IO operation has completed.
How should I write the following pseudcode?
I'm guessing each calling thread really needs to get its own Task, so each can get it's own continuation, so instead of returning currentTask I should return a new Task which is completed by the "inner" Task from DoAsyncNetworkIO.
Is there a clean way to do this, or do I have to hand roll it?
static object mutex = new object();
static Task currentTask;
async Task Fetch()
{
lock(mutex)
{
if(currentTask != null)
return currentTask;
}
currentTask = DoAsyncNetworkIO();
await currentTask;
lock(mutex)
{
var task = currentTask;
currentTask = null;
return task;
}
}

You could use a SemaphoreSlim to ensure that only one thread actually executes the background thread.
Assume your base task (the one actually doing the IO) is in a method called baseTask(), which I shall emulate like so:
static async Task baseTask()
{
Console.WriteLine("Starting long method.");
await Task.Delay(1000);
Console.WriteLine("Finished long method.");
}
Then you can initialise a SemaphoreSlim like so, to act a bit like an AutoResetEvent with initial state set to true:
static readonly SemaphoreSlim signal = new SemaphoreSlim(1, 1);
Then wrap the call to baseTask() in a method that checks signal to see if this is the first thread to try to run baseTask(), like so:
static async Task<bool> taskWrapper()
{
bool firstIn = await signal.WaitAsync(0);
if (firstIn)
{
await baseTask();
signal.Release();
}
else
{
await signal.WaitAsync();
signal.Release();
}
return firstIn;
}
Then your multiple threads would await taskWrapper() rather than awaiting baseTask() directly.
Putting that altogether in a compilable console application:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Demo
{
static class Program
{
static void Main()
{
for (int it = 0; it < 10; ++it)
{
Console.WriteLine($"\nStarting iteration {it}");
Task[] tasks = new Task[5];
for (int i = 0; i < 5; ++i)
tasks[i] = Task.Run(demoTask);
Task.WaitAll(tasks);
}
Console.WriteLine("\nFinished");
Console.ReadLine();
}
static async Task demoTask()
{
int id = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine($"Thread {id} starting");
bool firstIn = await taskWrapper();
Console.WriteLine($"Task {id}: executed: {firstIn}");
}
static async Task<bool> taskWrapper()
{
bool firstIn = await signal.WaitAsync(0);
if (firstIn)
{
await baseTask();
signal.Release();
}
else
{
await signal.WaitAsync();
signal.Release();
}
return firstIn;
}
static async Task baseTask()
{
Console.WriteLine("Starting long method.");
await Task.Delay(1000);
Console.WriteLine("Finished long method.");
}
static readonly SemaphoreSlim signal = new SemaphoreSlim(1, 1);
}
}
(The methods are all static because they are in a console app; in real code they would be non-static methods.)

await doesn't necessarily use continuations (the Task.ContinueWith kind) at all. Even when it does, you can have multiple continuations on one Task - they just can't all run synchronously (and you might run into some issues if you have a synchronization context).
Do note that your pseudo-code isn't thread-safe, though - you can't just do currentTask = DoAsyncNetworkIO(); outside of a lock. Only the await itself is thread-safe, and even then, only because the Task class that you're awaiting implements the await contract in a thread-safe way. Anyone can write their own awaiter/awaitable, so make sure to pay attention :)

Related

use asynchronous functions for I/O-bound operations

I was reading this doc https://learn.microsoft.com/en-us/dotnet/csharp/async which says that:
For I/O-bound code, you await an operation which returns a Task or Task inside of an async method.
I have two questions:
Q1- for I/O-bound code, does it mean we need to use Task.Factory.StartNew(..., TaskCreationOptions.LongRunning) or TaskCompletionSource
Q2- I wrote a two simple console apps below to simulate I/O-bound code, is my approach correct?
class Program //use Task.Factory.StartNew
{
static async Task Main(string[] args)
{
var task = ReadFile();
Console.WriteLine("Do other work");
int total = await task;
Console.WriteLine($"Read {total} lines");
Console.ReadLine();
}
static async Task<int> ReadFile()
{
return await Task.Factory.StartNew(new Func<int>(Read), TaskCreationOptions.LongRunning);
}
static int Read()
{
Thread.Sleep(5000); // simulate read operation
return 9999; // 9999 lines has been read
}
}
and
class Program // use TaskCompletionSource
{
static void Main(string[] args)
{
var awaiter = ReadFile().GetAwaiter();
Console.WriteLine("Do other work");
awaiter.OnCompleted(() => Console.WriteLine($"Read {awaiter.GetResult()} lines"));
Console.ReadLine();
}
static Task<int> ReadFile()
{
var tcs = new TaskCompletionSource<int>();
new Thread(() =>
{
tcs.SetResult(Read());
}).Start();
return tcs.Task;
}
static int Read()
{
Thread.Sleep(5000); // simulate read operation
return 9999; // 9999 lines has been read
}
}
Q1- for I/O-bound code, does it mean we need to use Task.Factory.StartNew(..., TaskCreationOptions.LongRunning) or TaskCompletionSource
No.
It means you use async and await.
Q2- I wrote a two simple console apps below to simulate I/O-bound code, is my approach correct?
No.
I/O is not synchronous by nature, so using Thread.Sleep is an incorrect substitute for I/O work. I/O is asynchronous by nature, so the proper placeholder is await Task.Delay.
class Program // use async/await
{
static async Task Main(string[] args)
{
var task = ReadFileAsync();
Console.WriteLine("Do other work");
var result = await task;
Console.WriteLine($"Read {result} lines");
Console.ReadLine();
}
static async Task<int> ReadFileAsync()
{
await Task.Delay(5000); // simulate read operation
return 9999; // 9999 lines has been read
}
}
In the general I/O case, there is no thread. This is why using Thread.Sleep throws everything off; it forces a thread to be used, when I/O doesn't need one.

Task.Status should be Running but shows RanToCompletion

I'm puzzled with this situation, where a class has a method that launches two periodic Tasks and then a property is used to check if both Tasks are still running or not, but the result is unexpected. Here is the code (simplified):
public partial class UdpClientConnector
{
Task localListener;
Task periodicSubscriber;
bool keepWorking = false;
public bool IsRunning
{
get
{
if ((localListener != null) && (periodicSubscriber != null))
{
return (localListener.Status == TaskStatus.Running) &&
(periodicSubscriber.Status == TaskStatus.Running);
}
else
return false;
}
}
public void Start()
{
keepWorking = true;
localListener = new Task(() => LocalListenerWorker());
localListener.Start();
periodicSubscriber = new Task(() => PeriodicSubscriberWorker());
periodicSubscriber.Start();
}
public void Stop()
{
keepWorking = false;
localListener.Wait();
periodicSubscriber.Wait();
}
async void LocalListenerWorker()
{
while (keepWorking)
{
// Do some work and then wait a bit
await Task.Delay(1000);
}
}
async void PeriodicSubscriberWorker()
{
while (keepWorking)
{
// Do some (other) work and then wait a bit
await Task.Delay(1000);
}
}
}
To test this boilerplate I used the following:
UdpClientConnector connector = new UdpClientConnector();
// This assert is successful because the two Tasks are not yet started
Assert.IsTrue(!connector.IsRunning);
// Starts the tasks and wait a bit
Connector.Start();
Task.Delay(2000).Wait();
// This fails
Assert.IsTrue(connector.IsRunning);
When I've tried to debug the test case, I've found that two Tasks are in the RanToCompletion state, which is unexpected due the fact that both tasks are just loops and should not terminate until keepWorking becomes false.
I've tried also to start the Tasks using Task.Factory.StartNew(..) with same results.
What I'm missing? Thank you!
The problem is with how you start the tasks, and indeed the task methods.
localListener = new Task(() => LocalListenerWorker());
That task will complete when LocalListenerWorker returns - which it will do pretty much immediately (when it hits the first await expression). It doesn't wait for the asynchronous operation to actually complete (i.e. the loop to finish).
async void methods should almost never be used - they're basically only there to support event handlers.
I suggest you rewrite your methods to return Task, and then use Task.Run to start them, passing in a method group:
Task.Run(LocalListenerWorker);
...
private async Task LocalListenerWorker()
{
// Body as before
}
The task by Task.Run will only complete when the task returned by LocalListenerWorker completes, which is when the loop body finishes.
Here's a complete demo:
using System;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
Task task1 = Task.Run(Loop);
// Don't do this normally! It's just as a simple demo
// in a console app...
task1.Wait();
Console.WriteLine("First task done");
Task task2 = new Task(() => Broken());
task2.Start();
// Don't do this normally! It's just as a simple demo
// in a console app...
task2.Wait();
Console.WriteLine("Second task done");
}
static async Task Loop()
{
for (int i = 0; i < 5; i++)
{
await Task.Delay(1000);
Console.WriteLine(i);
}
}
static async void Broken()
{
for (int i = 0; i < 5; i++)
{
await Task.Delay(1000);
Console.WriteLine(i);
}
}
}
The output shows:
0
1
2
3
4
First task done
Second task done
The first task behaves as expected, and only completes when the first async method has really completed. The second task behaves like your current code: it completes as soon as the second async method has returned - which happens almost immediately due to the await.

Monitoring Task completion

I run several tasks and keep them in a list to check if they are already completed.
I discovered that tasks that come from an async method are always shown as RanToCompletion although the task itself was still running.
Is there a way to get the "is complete" information from a Task object in both cases?
Here's a simple test case that shows this behaviour. I run two tasks, with/without an async method and check the states during and after completion.
private void test()
{
;
Action actionAsync = funcAsync;
Task taskAsync = Task.Run(actionAsync);
Action action = func;
Task task = Task.Run(action);
var statusAsync = taskAsync.Status;
var status = task.Status;
// stati are either WaitingToRun or Running
Thread.Sleep(TimeSpan.FromSeconds(2));
// Now it's quite certain, that both have started
var statusAsync2 = taskAsync.Status;
var status2 = task.Status;
Debug.Assert(statusAsync2 == TaskStatus.RanToCompletion);
Debug.Assert(status2 == TaskStatus.Running);
;
Thread.Sleep(TimeSpan.FromSeconds(12));
// Now it's quite certain, that both have finished
var statusAsync3 = taskAsync.Status;
var status3 = task.Status;
;
Debug.Assert(statusAsync3 == TaskStatus.RanToCompletion);
Debug.Assert(status3 == TaskStatus.RanToCompletion);
}
private async void funcAsync()
{
await Task.Delay(TimeSpan.FromSeconds(10));
}
private void func()
{
Thread.Sleep(TimeSpan.FromSeconds(10));
}
I discovered that tasks that come from an async method are always shown as RanToCompletion although the task itself was still running.
Yes, because your void method has completed, and that's all that Task.Run is calling. If instead you use:
private async Task FuncAsync()
{
await Task.Delay(TimeSpan.FromSeconds(10));
}
and use Func<Task> instead Action, then you'll call Task.Run(Func<Task>) and all will be well.
Short but complete example:
using System;
using System.Threading;
using System.Threading.Tasks;
class Test
{
static void Main()
{
Func<Task> func = FuncAsync;
Task task = Task.Run(func);
for (int i = 0; i < 7; i++)
{
Console.WriteLine(task.Status);
Thread.Sleep(1000);
}
}
private static async Task FuncAsync()
{
await Task.Delay(TimeSpan.FromSeconds(5));
}
}
Output:
WaitingForActivation
WaitingForActivation
WaitingForActivation
WaitingForActivation
WaitingForActivation
RanToCompletion
RanToCompletion
Try to avoid writing void async methods if you possibly can. They should basically only be used for event handlers.

Pausing a task within Task.Factory.StartNew

In my Asp.Net MVC 5 project I have a ~3 minute task that I pass to Task.Factory.StartNew().
I would like to pause the task from within the task if there is a validation issue in one of the steps of my code running in the task. I don't want to delay it async because the rest of the task will continue to run, which can't happen.
Could I use thread.sleep() without any repercussions since I'm within a task? I read that I may have to use TaskScheduler.Default to have the Task.Factory create a new thread for each task.
I'm using a PauseToken similar to a CancellationToken so I'll be able to resume the task or cancel this task based on user input.
Multithreading really scares me, and I don't want to overlook something.
Here is an example of the Thread.Sleep implementation:
public void WaitIfPaused(PauseToken pauseToken, CancellationToken cancellationToken, IProgressBar progressBar)
{
//TODO: If paused for an hour notify user via noty and abort the task to keep it from completing by cancellation via cancellationToken.
//wait for 1 hour
for (int i = 0; i < 3600; i++)
{
ThrowExceptionIfCancelled(cancellationToken, progressBar);
if (pauseToken.IsPaused)
{
Thread.Sleep(1000);
}
else
{
break;
}
}
}
PauseToken: http://blogs.msdn.com/b/pfxteam/archive/2013/01/13/cooperatively-pausing-async-methods.aspx
Requested: Implementation of task structure in shared code library.
public void StartTask(params object[] data)
{
//throw an exception if no ITask was found
if (_taskToRun == null)
throw new Exception("Task cannot be null");
//set up task cancellation
CancellationTokenSource = new CancellationTokenSource();
var cancellationToken = CancellationTokenSource.Token;
//set up task pausing
PauseTokenSource = new PauseTokenSource();
var pauseToken = PauseTokenSource.Token;
//start a new task using the Task that was set
_task = Task.Factory.StartNew(() => _taskToRun.Execute(cancellationToken, pauseToken, data), cancellationToken);
}
My Execute method that is invoked by _taskToRun.Execute:
Public override void Execute(CancellationToken cancellationToken, PauseToken pauseToken, params object[] data)
{
var managerList = (List<IFileManager>) data[0];
var instr = (List<InstructionSet>) data[1];
ProcessInstructions(managerList, instr, cancellationToken, pauseToken);
}
Update due to comments:
Code example: 3 instructions
For(var instruction in instructions)
{
instruction.Execute();
}
In my execute method I run into a scenario for pause and call WaitWhilePausedAsync from within the execute. It will continue to execute the other two instructions, but pause the only the current instructions execute method.
Edit: By awaiting instruction.Execute() it will wait until instruction.Execute() completes or is unpaused.
Final Edit:
I was able to resolve the issue by awaiting the Execute method and making it async like Servy and I3arnon suggested.
Final Code Sample:
foreach(var instruction in instructions)
{
try
{
await instruction.Execute(pauseToken);
}
catch(InvalidOperationException)
{
pauseTokenSource.IsPaused = true;
//ask if user wants to cancel or resume.
}
}
//Simplified
public async Task<bool> Execute(PauseToken pauseToken)
{
await pauseToken.WaitWhilePausedAsync();
//do work
}
You can safely use Thread.Sleep. The only drawback is that the thread would be wasted blocking synchronously.
You should be using await Task.Delay(1000) instead. The code after that line would not execute until the wait is complete, but you won't be wasting a thread in the meantime:
public async Task WaitIfPausedAsync(PauseToken pauseToken, CancellationToken cancellationToken, IProgressBar progressBar)
{
for (int i = 0; i < 3600; i++)
{
ThrowExceptionIfCancelled(cancellationToken, progressBar);
if (pauseToken.IsPaused)
{
await Task.Delay(1000)
}
else
{
break;
}
}
}
Edit: I was unaware of PauseToken.WaitWhilePausedAsync. You should definitly use that instead of replicating that yourself with polling over PauseToken.IsPaused

Multiple worker threads vs One worker with async/await

My code currently has the following 10 worker threads. Each worker thread continues polling a job from the queue and then process the long running job.
for (int k=0; k<10; k++)
{
Task.Factory.StartNew(() => DoPollingThenWork(), TaskCreationOptions.LongRunning);
}
void DoPollingThenWork()
{
while (true)
{
var msg = Poll();
if (msg != null)
{
Thread.Sleep(3000); // process the I/O bound job
}
}
}
I am refactoring the underlying code to use async/await pattern. I think I can rewrite the above code to the followings. It uses one main thread that keeps creating the async task, and use SemaphoreSlim to throttle the number of concurrent tasks to 10.
Task.Factory.StartNew(() => WorkerMainAsync(), TaskCreationOptions.LongRunning);
async Task WorkerMainAsync()
{
SemaphoreSlim ss = new SemaphoreSlim(10);
while (true)
{
await ss.WaitAsync();
Task.Run(async () =>
{
await DoPollingThenWorkAsync();
ss.Release();
});
}
}
async Task DoPollingThenWorkAsync()
{
var msg = Poll();
if (msg != null)
{
await Task.Delay(3000); // process the I/O-bound job
}
}
Both should behave the same. But I think the second options seems better because it doesn't block the thread. But the downside is I can't do Wait (to gracefully stop the task) since the task is like fire and forget. Is the second option the right way to replace the traditional worker threads pattern?
When you have code that's asynchronous, you usually have no reason to use Task.Run() (or, even worse, Task.Factory.StartNew()). This means that you can change your code to something like this:
await WorkerMainAsync();
async Task WorkerMainAsync()
{
SemaphoreSlim ss = new SemaphoreSlim(10);
while (true)
{
await ss.WaitAsync();
// you should probably store this task somewhere and then await it
var task = DoPollingThenWorkAsync();
}
}
async Task DoPollingThenWorkAsync(SemaphoreSlim semaphore)
{
var msg = Poll();
if (msg != null)
{
await Task.Delay(3000); // process the I/O-bound job
}
// this assumes you don't have to worry about exceptions
// otherwise consider try-finally
semaphore.Release();
}
Usually you don't use async/await inside a CPU-bound task. The method that starts such a task (WorkerMainAsync) can use async/await, but you should be tracking pending tasks:
async Task WorkerMainAsync()
{
SemaphoreSlim ss = new SemaphoreSlim(10);
List<Task> trackedTasks = new List<Task>();
while (DoMore())
{
await ss.WaitAsync();
trackedTasks.Add(Task.Run(() =>
{
DoPollingThenWorkAsync();
ss.Release();
}));
}
await Task.WhenAll(trackedTasks);
}
void DoPollingThenWorkAsync()
{
var msg = Poll();
if (msg != null)
{
Thread.Sleep(2000); // process the long running CPU-bound job
}
}
Another exercise would be to remove tasks from trackedTasks as they are finishing. For example, you could use ContinueWith to remove a finished tasks (in this case, remember to use lock to protect trackedTasks from simultaneous access).
If you really need to use await inside DoPollingThenWorkAsync, the code wouldn't change a lot:
trackedTasks.Add(Task.Run(async () =>
{
await DoPollingThenWorkAsync();
ss.Release();
}));
Note that in this case, you'd be dealing with a nested task here for the async lambda, which Task.Run will automatically unwrap for you.

Categories

Resources