Task.Run to increase parallelism of IO-bound operations? - c#

I'm getting a bit confused with Task.Run and all I read about it on the internet. So here's my case: I have some function that handles incoming socket data:
public async Task Handle(Client client)
{
while (true)
{
var data = await client.ReadAsync();
await this.ProcessData(client, data);
}
}
but this has a disadvantage that I can only read next request once I've finished processing the last one. So here's a modified version:
public async Task Handle(Client client)
{
while (true)
{
var data = await client.ReadAsync();
Task.Run(async () => {
await this.ProcessData(client, data);
});
}
}
It's a simplified version. For more advanced one I would restrict the maximum amount of parallel requests of course.
Anyway this ProcessData is mostly IO-bound (doing some calls to dbs, very light processing and sending data back to client) yet I keep reading that I should use Task.Run with CPU-bound functions.
Is that a correct usage of Task.Run for my case? If not what would be an alternative?

Conceptually, that is a fine usage of Task.Run. It's very similar to how ASP.NET dispatches requests: (asynchronously) reading a request and then dispatching (synchronous or asynchronous) work to the thread pool.
In practice, you'll want to ensure that the result of ProcessData is handled properly. In particular, you'll want to observe exceptions. As the code currently stands, any exceptions from ProcessData will be swallowed, since the task returned from Task.Run is not observed.
IMO, the cleanest way to handle per-message errors is to have your own try/catch, as such:
public async Task Handle(Client client)
{
while (true)
{
var data = await client.ReadAsync();
Task.Run(async () => {
try { await this.ProcessData(client, data); }
catch (Exception ex) {
// TODO: handle
}
});
}
}
where the // TODO: handle is the appropriate error-handling code for your application. E.g., you might send an error response on the socket, or just log-and-ignore.

Related

WebAPI HTTP request not completing until queued work kicks off on background task

In my .Net 6 WebPI service, I am queueing work to a background task queue, very closely based on the example here, but I could post parts of my implementation if that would help:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-6.0&tabs=visual-studio#queued-background-tasks
I am running into unexpected behavior where control is not returned to the caller, even after the return Ok(..) completes in the controller. Instead the request only completes after the await Task.Delay(1000); line is reached on the queued work item. The request returns to the client as soon as this line is reached, and does not need to wait for the Delay to finish.
I'm guessing this is because of the await either starting a new async context, or someone un-sticking the async context of the original request. My intention is for the request to complete immediately after queuing the work item.
Any insight into what is happening here would be greatly appreciated.
Controller:
public async Task<ActionResult> Test()
{
var resultMessage = await _testQueue.DoIt();
return Ok(new { Message = resultMessage });
}
Queueing Work:
public TestAdapter(ITaskQueue taskQueue)
{
_taskQueue = taskQueue;
}
public async Task<string> DoIt()
{
await _taskQueue.QueueBackgroundWorkItemAsync(async (_cancellationToken) =>
{
await Task.Delay(1000);
var y = 12;
});
return "cool";
}
IoC:
services.AddHostedService<QueueHostedService>();
services.AddSingleton<ITTaskQueue>(ctx =>
{
return new TaskQueue(MaxQueueCount);
});
TaskQueue:
private readonly Channel<BackgroundTaskContext> _queue;
public TaskQueue(int capacity)
{
var options = new BoundedChannelOptions(capacity)
{
FullMode = BoundedChannelFullMode.Wait
};
_queue = Channel.CreateBounded<BackgroundTaskContext>(options);
}
public async ValueTask QueueBackgroundWorkItemAsync(
Func<CancellationToken, ValueTask> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
await _queue.Writer.WriteAsync(new BackgroundTaskContext(workItem, ...));
}
Not sure what you expect here. I'm assuming you want the async method to return the cool in the api response. That's fine but because your also awaiting the async call with in DoIt(), then it pauses until QueueBackgroundWorkItemAsync finishes. You could remove the await and it will run and return as you expect.
I can't say that I'm a big fan of that design as you lose contact with it with exception of the cancellation token. Another thought would be to Send that work off to a console job or function app using message bus or even another http call.
Additional Notes:
Async can be complicated to explain because in reality, it wraps up code and executes on a thread of it's choosing. The await simulates the synchronous behavior.
await Task.Delay(1000); // Runs on it's own thread but still halts code execution for 1 second.
await _taskQueue.QueueBackgroundWorkItemAsync(async (_cancellationToken) // Waits for control to be returned from the code inside.
var resultMessage = await _testQueue.DoIt(); // Always waits for the code inside to complete.
If your wanting something to run without pausing code execution, you can either remove the await or add a Task.Run(() => { }); pattern. Is it a good idea is a whole other question. It also matters whether you need information back from the async method. If you don't await it then you'll get null back as it doesn't wait around for the answer to be computed.
This appears just to have been user error using the debugger. The debugger is switching to the background task thread and hitting breakpoints there before the response fully returns giving the appearance that control was not being returned to the client and being carried into the background task.
Even after adding some synchronous steps in QueueBackgroundWorkItemAsync and putting breakpoints on them, control was not immediately returned to the original http call. Only after I tried adding a long running task like await Task.Delay(1000); did enough time/ticks pass for the http response to return. I had conflated this with just the await somehow freeing up the original http context.

getAsync("Text") vs getAsync("text", cancellationToken)

I am using the oneDrive API aka Live SDK. using c# and XAML you can issue http GET request using the above mentioned method like so:
case A:
public async void methodA()
{
try
{
var meResult = await connectClient.GetAsync("me");
}
catch(Exception ex)
{
//exception handling
}
doSomething();
}
Case B:
public async void methodB()
{
try
{
var meResult = await connectClient.GetAsync("me", token);
}
catch(Exception ex)
{
//exception handling
}
doSomething();
}
where token is cancellationToken; that will wait for specific time and then cancel the request.
if there is no internet connection:
In case A the methodA() will hang on the getAsync and never progress so the doSomething() method is never called.
In case B the methodB() will handle the exception when the cancellationToken cancel the call then progress to the doSomething() method.
Now my concern and my question is:
I am afraid that keeping the await call hanging there will mean locking one thread and will affect the performance especially if the user clicked the button many times to call the methodA().
Is my concern justified?
is there a way for the .NET runtime to know that this awaited operation is timed out?? and eventually cancel it?
If you want to pass in a CancellationToken that times out after a certain amount of time, there is a constructor for CancellationTokenSource that does just that:
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var meResult = await connectClient.GetAsync("me", cts.Token);
By the way, even if you don't do this, a .NET thread won't be locked while you wait for GetAsync to finish - that's one of the direct benefits of using await instead of .Result.
Related article by Stephen Toub: Coalescing CancellationTokens from Timeouts

Immediately cancelling blocking operation with timeout

I have a blocking operation that reads from a queue, but it can take a timeout. I can easily convert this to an "async" operation:
public async Task<IMessage> ReceiveAsync(CancellationToken cancellationToken)
{
return await Task.Run(() =>
{
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
// Try receiving for one second
IMessage message = consumer.Receive(TimeSpan.FromSeconds(1));
if (message != null)
{
return message;
}
}
}, cancellationToken).ConfigureAwait(false);
}
Aborting a thread is generally considered bad practice since you can leak resources, so the timeout seems like the only way to cleanly stop a thread. So I have three questions:
What is a generally accepted timeout value for "immediate" cancellation?
For libraries that provide built-in async methods, does immediate cancellation truly exist or do they also use timeouts and loops to simulate it? Maybe the question here is how would you make use of software interrupts and if these also have to do some sort of polling to check if there are interrupts, even if it's at the kernel/CPU level.
Is there some alternate way I should be approaching this?
Edit: So I may have found part of my answer with Thread.Interrupt() and then handling ThreadInterruptedException. Is this basically a kernel-level software interrupt and as close to "immediate" as we can get? Would the following be a better way of handling this?
public async Task<IMessage> ReceiveAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var completionSource = new TaskCompletionSource<IMessage>();
var receiverThread = new Thread(() =>
{
try
{
completionSource.SetResult(consumer.Receive());
}
catch (ThreadInterruptedException)
{
completionSource.SetCanceled();
}
catch (Exception ex)
{
completionSource.SetException(ex);
}
});
cancellationToken.Register(receiverThread.Interrupt);
receiverThread.Name = "Queue Receive";
receiverThread.Start();
return await completionSource.Task.ConfigureAwait(false);
}
It depends on your specific needs. A second could be immediate for some and slow for others.
Libraries (good ones) which provide async API do so from the bottom up. They usually don't wrap blocking (synchronous) operations with a thread to make them seem asynchronous. They use TaskCompletionSource to create truly async methods.
I'm not sure what you mean by queue (the built-in Queue in .Net doesn't have a Receive method) but you should probably be using a truly async data structure like TPL Dataflow's BufferBlock.
About your specific code sample.
You are holding up a thread throughout the entire operation (that's async over sync) which is costly. You could instead try to consume quickly and then wait asynchronously for the timeout to end, or for the CancellationToken to be cancelled.
There's also no point in using another thread with Task.Run. You can simply have the async lambda be the content of ReceiveAsync:
public async Task<IMessage> ReceiveAsync(CancellationToken cancellationToken)
{
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
// Try receiving for one second
IMessage message;
if (!consumer.TryReceive(out message))
{
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
}
if (message != null)
{
return message;
}
}
}
If your queue implements IDisposable a different (harsher) option would be to call Dispose on it when the CancellationToken is cancelled. Here's how.

Running several infinite loops with async/await

I am developing android messanger app based on xamarin and .net 5 async/awaits.
In my app i have producer/consumer pattern for processing messages which is made on infinite loops.
for example ReadTcpClientAsync producer:
async Task ReadTcpClientAsync(CancellationToken cancellationToken)
{
cde.Signal();
while (!cancellationToken.IsCancellationRequested)
{
byte[] buffer = await atc.ReadAsync(cancellationToken);
// queue message...
}
}
or SendStatementsAsync consumer which deque messages and awaits WriteAsync
private async Task SendStatementsAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
var nextItem = await _outputStatements.Take();
cancellationToken.ThrowIfCancellationRequested();
// misc ...
await atc.WriteAsync(call.Serialize());
}
}
and some consumers just await on Take calls
var update = await _inputUpdateStatements.Take();
this construction works pretty well on tests, but there is one method where i think i made a huge mistake.
this method intent to run entire client backend, starting 3 pro/con while (true) loops simultaneously.
here it is:
public async Task RunAsync()
{
_isRunning = true;
_progress.ProgressChanged += progress_ProgressChanged;
await InitMTProto(_scheme).ConfigureAwait(false); // init smth...
// various init stuf...
await atc.ConnectAsync().ConfigureAwait(false); // open connection async
// IS IT WRONG?
try
{
await Task.WhenAny(SendStatementsAsync(_cts.Token),
ReadTcpClientAsync(_cts.Token),
ProcessUpdateAsync(_cts.Token, _progress)).ConfigureAwait(false);
}
catch (OperationCanceledException oce)
{
}
catch (Exception ex)
{
}
}
Forget about android for now, think any UI (WinForm, WPF, etc) OnCreate method in UI context to call RunAsync
protected async override void OnCreate(Bundle bundle)
{
// start RA
await client.RunAsync()
// never gets here - BAD, but nonblock UI thread - good
Debug.WriteLine("nevar");
}
so, as you can see there is a problem. I can't do anything after RunAsync await call because it will never returns from Task.WhenAny(...). And i need perform status check there, but i need this pro/cons methods started, because my check wait on ManualResetEvent for it:
if (!cde.Wait(15000))
{
throw new TimeoutException("Init too long");
}
Also, my check is async too, and it works like a charm :)
public async Task<TLCombinatorInstance> PerformRpcCall(string combinatorName, params object[] pars)
{
// wait for init on cde ...
// prepare call ...
// Produce
ProduceOutput(call);
// wait for answer
return await _inputRpcAnswersStatements.Take();
}
I think i should use another approach for starting this infinite loops, but i already have async Task methods all the way - so i really have no idea what to do.
Any help please?
Ok, after a lot of reading (nothing found) and #svick's advice i decided to call this methods without "await" as separate Task.Run's.
Aso i decided to run it in ThreadPool.
My final code is:
try
{
/*await Task.WhenAny(SendStatementsAsync(_cts.Token),
ReadTcpClientAsync(_cts.Token),
ProcessUpdateAsync(_cts.Token, _progress)).ConfigureAwait(false);*/
Task.Run(() => SendStatementsAsync(_cts.Token)).ConfigureAwait(false);
Task.Run(() => ReadTcpClientAsync(_cts.Token)).ConfigureAwait(false);
Task.Run(() => ProcessUpdateAsync(_cts.Token, _progress)).ConfigureAwait(false);
Trace.WriteLineIf(clientSwitch.TraceInfo, "Worker threads started", "[Client.RunAsync]");
}
Everything works fine as expected..
i'm not sure what problems it will cause in exception handling, as i know they will be lost
Of course such calls produce warning
Because this call is not awaited, execution of the current method
continues before the call is completed. Consider applying the 'await'
operator to the result of the call.
which can be easily suppressed this way
// just save task into variable
var send = Task.Run(() => SendStatementsAsync(_cts.Token)).ConfigureAwait(false);
Also, if anyone know better solution i will be grateful to hear it.

WCF Async Operation + IO Operation

What is the advantage of writing the following WCF service operation using Async CTP?
Task.Factory.StartNew will anyway block the threadpool thread for the duration of the longRunningIOOperation?
public Task<string> SampleMethodAsync(string msg)
{
return await Task.Factory.StartNew(() =>
{
return longRunningIOOperation();
});
}
Is there a better way to write this so we take advanage of IO completion threads?
You'll need to make the longRunningIOOperation an asynchronous operation as well. As long as any operation in your code blocks the thread, some thread will be blocked, whether it's a threadpool one or the one in which your operation was called. If your operation is asynchronous, you can write something similar to the code below.
public Task<string> SampleMethodAsync(string msg)
{
var tcs = new TaskCompletionSource<string>();
longRunningIOOperationAsync().ContinueWith(task =>
{
tcs.SetResult(task.Result);
});
return tcs.Task;
}
Finally I figured out how this works. I installed .net FX4.5 and everything worked like a charm.
In my scenario, Service A makes a call to Service B like this.
public class ServiceA : IServiceA
{
public async Task<string> GetGreeting(string name)
{
ServiceBClient client = new ServiceBClient();
return await client.GetGreetingAsync();
}
}
client.GetGreetingAsync() takes 10 seconds to process. My understading is Service A request thread will not be blocked by calling GetGreetingAsync().
Can you explaing how this is implemented by WCF behind the scenes or point me to some documentation to understand how all this works from the perspective of WCF?

Categories