use asynchronous functions for I/O-bound operations - c#

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.

Related

Switch new Task(()=>{ }) for Func<Task>

In an answer to one of my other questions, I was told that use of new Task(() => { }) is not something that is a normal use case. I was advised to use Func<Task> instead. I have tried to make that work, but I can't seem to figure it out. (Rather than drag it out in the comments, I am asking a separate question here.)
My specific scenario is that I need the Task to not start right when it is declared and to be able to wait for it later.
Here is a LinqPad example using new Task(() => { }). NOTE: This works perfectly! (Except that it uses new Task.)
static async void Main(string[] args)
{
// Line that I need to swap to a Func<Task> somehow.
// note that this is "cold" not started task
Task startupDone = new Task(() => { });
var runTask = DoStuff(() =>
{
//+++ This is where we want to task to "start"
startupDone.Start();
});
//+++ Here we wait for the task to possibly start and finish. Or timeout.
// Note that this times out at 1000ms even if "blocking = 10000" below.
var didStartup = startupDone.Wait(1000);
Console.WriteLine(!didStartup ? "Startup Timed Out" : "Startup Finished");
await runTask;
Console.Read();
}
public static async Task DoStuff(Action action)
{
// Swap to 1000 to simulate starting up blocking
var blocking = 1; //1000;
await Task.Delay(500 + blocking);
action();
// Do the rest of the stuff...
await Task.Delay(1000);
}
I tried swapping the second line with:
Func<Task> startupDone = new Func<Task>(async () => { });
But then the lines below the comments with +++ in them don't work right.
I swapped the startupDone.Start() with startupDone.Invoke().
But startupDone.Wait needs the task. Which is only returned in the lambda. I am not sure how to get access to the task outside the lambda so I can Wait for it.
How can use a Func<Task> and start it in one part of my code and do a Wait for it in another part of my code? (Like I can with new Task(() => { })).
The code you posted cannot be refactored to make use of a Func<Task> instead of a cold task, because the method that needs to await the task (the Main method) is not the same method that controls the creation/starting of the task (the lambda parameter of the DoStuff method). This could make the use of the Task constructor legitimate in this case, depending on whether the design decision to delegate the starting of the task to a lambda is justified. In this particular example the startupDone is used as a synchronization primitive, to signal that a condition has been met and the program can continue. This could be achieved equally well by using a specialized synchronization primitive, like for example a SemaphoreSlim:
static async Task Main(string[] args)
{
var startupSemaphore = new SemaphoreSlim(0);
Task runTask = RunAsync(startupSemaphore);
bool startupFinished = await startupSemaphore.WaitAsync(1000);
Console.WriteLine(startupFinished ? "Startup Finished" : "Startup Timed Out");
await runTask;
}
public static async Task RunAsync(SemaphoreSlim startupSemaphore)
{
await Task.Delay(500);
startupSemaphore.Release(); // Signal that the startup is done
await Task.Delay(1000);
}
In my opinion using a SemaphoreSlim is more meaningful in this case, and makes the intent of the code clearer. It also allows to await asynchronously the signal with a timeout WaitAsync(Int32), which is not something that you get from a Task out of the box (it is doable though).
Using cold tasks may be tempting in some cases, but when you revisit your code after a month or two you'll find yourself confused, because of how rare and unexpected is to have to deal with tasks that may or may have not been started yet.
I always try my hardest to never have blocking behavior when dealing with anything async or any type that represents potential async behavior such as Task. You can slightly modify your DoStuff to facilitate waiting on your Action.
static async void Main(string[] args)
{
Func<CancellationToken,Task> startupTask = async(token)=>
{
Console.WriteLine("Waiting");
await Task.Delay(3000, token);
Console.WriteLine("Completed");
};
using var source = new CancellationTokenSource(2000);
var runTask = DoStuff(() => startupTask(source.Token), source.Token);
var didStartup = await runTask;
Console.WriteLine(!didStartup ? "Startup Timed Out" : "Startup Finished");
Console.Read();
}
public static async Task<bool> DoStuff(Func<Task> action, CancellationToken token)
{
var blocking = 10000;
try
{
await Task.Delay(500 + blocking, token);
await action();
}
catch(TaskCanceledException ex)
{
return false;
}
await Task.Delay(1000);
return true;
}
First, the type of your "do this later" object is going to become Func<Task>. Then, when the task is started (by invoking the function), you get back a Task that represents the operation:
static async void Main(string[] args)
{
Func<Task> startupDoneDelegate = async () => { };
Task startupDoneTask = null;
var runTask = await DoStuff(() =>
{
startupDoneTask = startupDoneDelegate();
});
var didStartup = startupDoneTask.Wait(1000);
Console.WriteLine(!didStartup ? "Startup Timed Out" : "Startup Finished");
}

Task.Delay() does not delay

Trying to understand async/await in the sample below:
public static Task <string> job()
{
Console.WriteLine("start delay 1");
Task.Delay(2000);
Console.WriteLine("sleep done 1");
Task<string> t = new Task<string>(()=> {
Console.WriteLine("start delay 2");
Task.Delay(3000);
Console.WriteLine("sleep done 2");
return "aaaa"; }) ;
t.Start();
return t;
}
public async static void caller()
{
string s = await job();
Console.WriteLine("result is {0}", s);
}
public static void Main()
{
caller();
Console.WriteLine("done with caller");
Console.ReadLine();
}
To get a more clear picture I would like to make task run a bit longer and I have added Task.Delay(). Both delays are taking no time and finish immediately. Why? How to make my task spend some time on simple job to make it last longer?
Task.Delay creates a Task that delays for the time specified. You do spawn a Task that Delays for 2 Seconds but you never wait for it.
adding await to the call does fix this. (You have to mark the method async)
public static async Task <string> job()
{
...
await Task.Delay(2000);
...
}
You can read more about Asynchronous Programming here
Task.Delay returns you a task that will complete in the given milliseconds you have provided. To hold execution of your code at this point (this is a massive simplification of what it does and you should definately do more learning on async/await in c#) you need to await the call to Task.Delay(2000) like this
await Task.Delay(2000);
to use the await key word you need to mark your method signature as async like this
public static async Task<string> job()
but I would highly recommend reading up on async and await

Execute method in a loop after a minimum time interval using Task.WhenAll or similar

Async beginner here, trying to improve this skill.
I want to execute a method at most every 500 miliseconds, or longer if a given execution takes longer than that. In that case the loop can continue imediately.
I know that I could do that with a StopWatch or some other timer, but I would like to do that with async/await constructs.
I developed a quick spike, but when I run it it seems to execute very tightly, and not once very 500ms or more. What am I forgeting to "await"?
class Program
{
static void Main(string[] args)
{
while (true)
{
Run();
}
}
static async void Run()
{
var task1 = Task.Delay(500);
var task2 = DoSomethingAsync();
await Task.WhenAll(task1, task2);
}
static async Task DoSomethingAsync()
{
Random r = new Random();
int miliseconds = Convert.ToInt32(r.NextDouble() * 1000);
await Task.Delay(miliseconds);
Console.WriteLine(miliseconds);
}
}
You do not await the toplevel task (Since you do not have one). Try this:
class Program
{
static void Main(string[] args)
{
while (true)
{
Run().Wait(); // Wait on each iteration.
}
}
// Need to return Task to be able to await it.
static async Task Run()
{
var task1 = Task.Delay(500);
var task2 = DoSomethingAsync();
await Task.WhenAll(task1, task2);
}
static async Task DoSomethingAsync()
{
Random r = new Random();
int miliseconds = Convert.ToInt32(r.NextDouble() * 1000);
await Task.Delay(miliseconds);
Console.WriteLine(miliseconds);
}
}
Now, there is a catch in using .Wait(): it blocks execution. See this excellent guide: https://msdn.microsoft.com/en-us/magazine/jj991977.aspx for more details. But don't worry, for the Main method of a Console App it is perfectly safe.
The same guide explains also why you should always try to return a Task instead of using async void MyTask() { ... }. In your case it prevents you from waiting for the Run() method to complete.

How to have mutliple threads await a single Task?

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 :)

Async Task vs Task

There is Async method FuncAsync and Task object t.
Both are doing same thing, both using the thread pool.
What's difference between these two strategies?
class Program
{
private static async Task<int> FuncAsync(string str)
{
string s = await Task.Run(() =>
{
Console.WriteLine("{0} <- Pooled? ID -> {1}", Thread.CurrentThread.IsThreadPoolThread, Thread.CurrentThread.ManagedThreadId);
// Thread.Sleep(1000);
return str;
});
return s.Length;
}
public static void Main(string[] args)
{
Task<int> z = FuncAsync("Text");
Console.WriteLine(z.Result);
Task<int> t = Task.Run(() =>
{
Console.WriteLine("{0} <- Pooled? ID -> {1}", Thread.CurrentThread.IsThreadPoolThread, Thread.CurrentThread.ManagedThreadId);
// Thread.Sleep(1000);
return "Text".ToString().Length;
});
Console.WriteLine(t.Result);
Thread.CurrentThread.Join(1000);
Console.ReadLine();
}
}
I think that with Async method waiting Result and can not be close without finish task,
and with Task - thread is background and may be closed without finish task.
One is a reusable function, the other is not. One passes the string length back across threads, the other passes the entire string. Neither is actually running asynchronously because you are immediately asking for the .Result which blocks until the task completes (you should use await instead)

Categories

Resources