Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Changing a synchronous method to async

I've googled around a lot and read different noob tutorials, but I don't think I understand what the proper thing to do is. Basically there is existing code that is synchronous that does something if the server is up and running. Sometimes, very rarely, the server takes longer to come up so I wanted to wrap it in some retry logic. I built a completely stupid console app to try to understand how async and await work a little bit and came up with this:

    private static int counter = 0;

    static void Main(string[] args)
    {
        DoIt();
        Console.ReadLine();
    }

    static bool LongTask()
    {
        if (counter == 2)
        {
            Console.WriteLine("finally true");
            Thread.Sleep(1000);
            return true;
        }
        counter++;
        Console.WriteLine("false");
        Thread.Sleep(1000);
        return false;
    }

    public static Task<bool> WrapperLongTask()
    {
        Console.WriteLine("wrapper called");
        return Task.Run(() => LongTask());            
    }

    public static async Task DoIt()
    {
        Console.WriteLine("hi");
        var result = await WrapperLongTask();
        while (result != true)
        {
            result = await WrapperLongTask();
            Console.WriteLine("inside while loop");
        }
        Console.WriteLine($"outside while loop {result}");
        Console.WriteLine("bye");
    }

My LongTask function is representing my current function that does work that usually works the first time. Is it ok practice to then call this method with

Task.Run(() => LongTask())

Assuming that is 'ok', then I would basically create this in my actual code for my current method DoWork().

Task DoWorkAsync(....) {
     return Task.Run(() => DoWork()
}

Basically just wrapping it in a Task.Run, changing the return type to Task. Then when I call this method later, I would do something like

var x = await DoWorkAsync;
// do more stuff with x

Is this way I should convert a previous sync method async?

Edit

pseudo code for DoWork(string directory, CancellationToken token)

var files = Directory.GetFiles(directory, "*", SearchOption.AllDirectories);
foreach (var file in files) {
    try {
       token.ThrowIfCancellationRequested();
       var fileName = Path.GetFileName(file);
       // check if file already exists on server, if not, upload it
    }
    catch (Exception exception) {
       // error handling
    }
}
like image 843
Crystal Avatar asked Jan 07 '16 01:01

Crystal


1 Answers

The short answer is No, you cannot convert all types of synchronous code to asynchronous simply by wrapping the operation with Task.Run and making the method return Task.

Usually, asynchronous code makes sense when the operation in consideration might invoke some IO operations (file system read/write, network or web access, database access ... etc.).

For example, if you have a method that reads some data from a file using synchronous methods like FileStream.Read, and then does some CPU work on the contents of such file, then you can convert your method to be asynchronous by making it invoke FileStream.ReadAsync instead and then asynchronously wait until ReadAsync is done by using the await keyword and then work on the contents of the file (of course you have to change the method to return Task and to be async).

The benefit in this case is that there is no thread waiting for the IO operation to complete and threads are expensive.

The benefit of not having threads waiting for IO operations to complete is very important in server applications like ASP.NET web sites where you expect a lot of simultaneous requests. However, for simple applications you might not want to bother with asynchronous code in the first place.

You can use Task.Run if you want to run multiple CPU intensive operations on multiple CPU cores.

For example, if you have 4 CPU cores, it makes sense to create 4 tasks via Task.Run to process some data. Consider the previous example, after you asynchronously wait for ReadAsync to complete, you can split the result of the read to 4 parts (assuming that the data is relatively large), and create 4 tasks via Task.Run, each will handle one part of the results. You can then asynchronously wait for the 4 tasks to complete using Task.WhenAll.

like image 185
Yacoub Massad Avatar answered Oct 15 '22 11:10

Yacoub Massad