Search code examples
c#.netasync-awaittask-parallel-librarywebclient

Async and sync versions of method


Ok so I have been reading up a lot and working on the best ways to use async methods and task, etc. I believe I'm (mostly) understanding it but I want to check to make sure.

I did start making async wrappers for sync tasks using Task.Run() but recently read that it's much better to just have a sync method and let the application determine if it needs to be pushed to another thread by using Task.Run() itself, which makes sense. However, I've also read that the exception to this is for naturally async functions. I'm not sure I fully understand 'naturally async' but as a basic understanding it seems that .NET framework methods that offer async methods are naturally async and WebClient.DownloadFile/DownlodFileAsync is one of those.

So I have two methods and if anyone would be willing to give feedback I would like to test my understanding and make sure this is correct.

Method one is for moving some files around on the local operating system and looks like this (pseudo-code):

Public static void MoveStagingToCache()
{
    ...do some file move, copy, delete operations...
}

The second looks like this (pseudo-code):

Public static void DownloadToCache()
{
    ...do some analysis to get download locations...
    using (var wc = new WebClient())
    {
        wc.DownloadFile(new Uri(content.Url), targetPath);
    }
    ...do other stuff...
}

So my understanding is as follows. The first method should be left as is, since none of the file operation methods have async versions and are therefore not likely naturally async. It would be up to the caller to determine between just calling MoveStagingToCache() to run sync or calling Task.Run(()=>MoveStagingToCache()) to push it to a background thread.

However, in the second method, the download is naturally async (unless I misunderstand) so it could create a sync and async version. In order to do that I should NOT just wrap the sync method like this:

Public static Task DownloadToCacheAsync()
{
    return Task.Run(()=>DownloadToCache());
}

Instead, I should make the core method async as follows:

Public static async Task DownloadToCache()
{
    ...do some analysis to get download locations...
    using (var wc = new WebClient())
    {
        await wc.DownloadFileTaskAsync(new Uri(content.Url), targetPath);
    }
    ...do other stuff...
}

and then I can create the sync version like this:

Public static void DownloadToCache()
{
    DownloadToCacheAsync().Wait();
}

This allows for use of the naturally async method and also offers a sync overload for those that need it.

Is that a good understanding of the system or am I messing something up?

Also as an aside, is the difference in WebClient.DownloadFileAsync and WebClient.DownloadFileTaskAsync just that the task returns a task for error catching?

EDIT

Ok so after some discussion in the comments and answers I realized I should add some more detail on my system and intended use. This is inside a library that is intended to be run on desktop, not ASP. As such, I'm not concerned about saving threads for request processing, the main concern is to keep the UI thread open and responsive to the user and to push 'background' tasks to another thread that can be processed by the system while the user goes about doing what they need to do.

For MoveStagingToCache, this will be called on program startup, but I don't need to wait for it to complete to continue loading or using the program. In most cases it will complete before the rest of the program is up and running and lets the user do anything most likely (probably 5-10 seconds run time max), but even if it's not complete before user interaction starts the program will work fine. Because of that my main desire here is to move this operation off the UI thread, start it working, and continue on with the program.

So for this one, my current understanding is that this method should be sync in the library, but then when I call it from the main (UI) thread I just use

Task.Run(()=>MoveStagingToCache());

Since I don't really need to do anything when complete, I really don't need to await this right? If I just do the above it will start the operation on a background thread?

For DownloadToCache, similar but a little different. I want the user to be able to initiate the download operation, then continue working in the UI until the download is complete. After it's complete I will need to do some simple operations to notify the user that it's ready to go and enable the 'use it' button, etc. In this case, my understanding is that I would create this as an async method that awaits the WebClient download call. This would push it off the UI thread for the actual download, but then would return once download is complete to allow me to do whatever UI updates are needed after the await call.

Correct?


Solution

  • You have asked multiple intertwined questions, let me make an attempt to answer most of them, to begin with read articles by Stephen Cleary for much more clarity on the working of Async, especially regarding Naturally Async - There's no thread, this will help in understanding what a naturally Async method means.

    Async Operations

    There are two kind of Async operations IO bound and CPU bound

    How does IO bound Async works

    IO bound are operations which leave the process boundary to make a call to the external service (database, Web Service, Rest API), for which synchronous calls leads to blocking a thread, waiting idle for the out of process call to return, but in case of Async operations calling thread can be released to process other calls.

    What happens on IO Async call return

    When the call returns, any of the worker thread can be assigned to complete the call, which can be indicated using ConfigureAwait(false) on a Task object. By virtue of IO bound Async design, system can achieve very high scalability, since the thread, which is a precious / limited resource per process is not wasted in idle waiting.

    True Async / Natural Async / IO Async

    A True Async request can serve millions of request, compared to few 100 by synchronous system, underlying technology is called as IO completion ports in windows. It doesn't need a software thread to process the request, it use the hardware based concurrency

    CPU Bound Async

    On other hand for the CPU bound async use case is in case of WPF or similar thick clients, which can take background processing on a separate thread, using Task.Run, thus free up the Ui thread, keeping system much more responsive, here you don't save a thread, since a worker thread is invoked using Task.Run, its all in same process, but system is much more responsive, which is an important achievement via async processing.

    I did start making async wrappers for sync tasks using Task.Run() but but recently read that it's much better to just have a sync method and let the application determine if it needs to be pushed to another thread by using Task.Run()

    There's no automatic way for the application to decide, its either your code or the code that you are calling which would create a worker thread. For IO bound Async, you need to call the correct API, which would be internally harnessing the Async pipeline exposed by the Windows (IO completion port) to make it pure Async

    I'm not sure I fully understand 'naturally async' but as a basic understanding it seems that .NET framework methods that offer async methods are naturally async and WebClient.DownloadFile/DownlodFileAsync is one of those.

    Most of the .Net methods offering Async version would be IO bound call, CPU bound need to be explicitly created using Task.Run and then awaited. For event based Async implementation TaskCompletionSource is a good option, which returns an ongoing awaitable Task, which can be SetResult, SetException for setting the result or error in an event thus asynchronously completing the Task. TaskCompletionSource is also a threadless implementation

    Regarding different methods that you have posted

    Your understanding regarding Method1 is correct, user can decide whether to use a background thread to call it as it free up the main calling thread , which can be a Ui thread. Regarding Method2, as it has been mentioned in the comments, make sure it is Async all the way to the top (entry point), as that's the only way to release the calling thread to be utilized in other operations

    Finally

    This code is very bad idea, also mentioned in the comments by @LexLi

    Public static Task DownloadToCacheAsync()
    {
        return Task.Run(()=>DownloadToCache());
    }
    
    Public static void DownloadToCache()
    {
        DownloadToCacheAsync().Wait();
    }
    

    Couple of points:

    1. Doing Task.Run on a IO bound Synchronous method, would not have any advantage, since the worker thread is anyway blocked, so a thread is waiting idle unlike Async method, there's no gain in system scalability, since threads are blocked
    2. If a method has Async version, then it will surely have the Sync version, which needs to be used simply DownloadToCache(). Sync precedes Async and I am not aware of the case with only Async call but no Sync
    3. An explicit Task.Wait() is executed in the calling thread, which makes the system un-responsive and can even in few cases lead to deadlock as main thread is blocked, while other operations need to be done on the main / calling thread
    4. Only reason for Task.Wait() usage is processing some logic on a background / worker / Thread pool thread, which need to be waited and may be use the result before further logical processing, mostly for the CPU bound operations. This is not for the IO bound operations

    I hope this helps in providing some clarity regarding the usage of the Async-Await apis