简体   繁体   中英

Async/await: a correct way to make background task non-blocking

EDIT: from OP's comment, the goal is non-blocking background task so that the rest remain responsive

Say I have a function like this:

void OnFrameSampleAcquired(VideoCaptureSample sample)
{  
    //Some code here

    //Here I want to introduce an Asynchrnous process
    ProcessAsynchronously(_latestImageBytes);

    //some more code here
}

In the point commented, I want to introduce a call to an asynchronous function. Now, I can not modify OnFrameSampleAcquired (meaning I can not make it "async"). How can I do this?

I am thinking

async void ProcessAsynchronously(byte[] image)
{
    await Process1(image);
    await Process2(image);
    // ...
}

or async Task ProcessAsynchronously(byte[] image)

where ProcessX are also declared as async

Is this a good approach?

Thanks for any insight, since my practical experience with asynchronous processing is very few.

EDITED included all suggestions from the comments and added some background.

Background & insights

Converting one function to async won't be enough to make the whole process non-blocking. To achieve what you want, the whole processing path (call stack) should be converted to non-blocking. One blocking method on your call stack is enough to render the whole process blocking. Non-blocking does not necessarily mean async , as some of the examples below will show.

There are two steps involved in converting a method into async :

  • Change the signature to return Task . This will allow your callers track status and outcome of your method.
  • Add async keyword to the signature. This will allow await -ing other async methods in the body of your method.

CPU-bound vs IO-bound tasks

Note that even when you await for a method, it doesn't necessarily mean that you immediately release the thread back to your caller. The method being await -ed will release the thread back to you only as soon as it in turn begins await -ing for an IO-bound operation. Your thread will still block while the await -ed method performs CPU-bound operations before the first time it is await -ing for an IO-bound operation.

For example:

async Task MyAsyncMethod()
{
    Thread.Sleep(5000); // equivalent to CPU-bound operations
}

await MyAsyncMethod(); // will block for 5 seconds

On the other hand,

async Task MyAsyncMethod()
{
    await Task.Delay(5000); // equivalent to IO-bound operations
}

await MyAsyncMethod(); // will return immediately

And the workaround if you have CPU-bound tasks but still don't want to block the caller thread:

async Task MyAsyncMethod()
{
    await Task.Yield(); // this does the magic
    Thread.Sleep(5000); // equivalent to CPU-bound operations
}

await MyAsyncMethod(); // will return immediately thanks to Task.Yield()

What to do in your case

Since I'm not sure why you cannot change OnFrameSampleAcquired signature to async , I will suggest several different options.

Option 1

The simplest and the truly asynchronous approach would be this:

async Task OnFrameSampleAcquired(VideoCaptureSample sample)
{  
    //Some code here

    //Here I want to introduce an Asynchrnous process
    await ProcessAsynchronously(_latestImageBytes);

    //some more code here -- provided it is either async or non-blocking!
}

async Task ProcessAsynchronously(byte[] image)  
{
    await Process1(image);
    await Process2(image);
    // ...
}

If all of the methods on your processing path look like these, you have a properly implemented non-blocking background job.

Option 2

If you're absolutely unable to change the signature OnFrameSampleAcquired, there is a workaround. You can instead invoke the rest of the processing asynchronously, as suggested by @Fildor:

public void OnFrameSampleAcquired(VideoCaptureSample sample)
{
    //Some code here

    //Here I want to introduce an Asynchrnous process
    ProcessAsynchronously(_latestImageBytes).ContinueWith(task => {
        // this runs on a different thread after ProcessAsynchronously is completed
        // some more code here
    });

    // return without blocking
}

Here you win on both sides: first, you don't have to change the signature of OnFrameSampleAcquired; second, OnFrameSampleAcquired is now a non-blocking method.

Option 3

If you cannot change your signature because you must implement an interface like this:

public interface ISomeInterface
{
    void OnFrameSampleAcquired(VideoCaptureSample sample);
    // ... other members
}

then you can add the async keyword to your method and still comply with the interface:

async void OnFrameSampleAcquired(VideoCaptureSample sample)
{  
    //Some code here

    //Here I want to introduce an Asynchrnous process
    await ProcessAsynchronously(_latestImageBytes);

    //some more code here
}

ISomeInterface x;                  // initialized elsewhere
x.OnFrameSampleAcquired(/*....*/); // the same as await, but no error handling

The drawback of this option is that the callers cannot track nor the status of the task (still running or completed?), neither its outcome (completed or threw exception?). You will probably have to wrap the entire body of OnFrameSampleAcquired in a try/catch , and write an exception to log.

Option 4

Technically, you can also invoke an async ProcessAsynchronously from a non-async OnFrameSampleAcquired using Wait on a Task , but it won't achieve your goal of having a non-blocking background task . The Wait() will block the thread until the async processing is done:

void OnFrameSampleAcquired(VideoCaptureSample sample)
{  
    //Some code here

    //Here I want to introduce an Asynchrnous process
    ProcessAsynchronously(_latestImageBytes).Wait();

    //some more code here
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM