繁体   English   中英

异步/等待:使后台任务不阻塞的正确方法

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

编辑:从OP的评论,目标是非阻塞后台任务,以便其余保持响应

说我有一个这样的功能:

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

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

    //some more code here
}

在评论中,我想介绍一个对异步函数的调用。 现在,我无法修改OnFrameSampleAcquired (这意味着我无法使其“异步”)。 我怎样才能做到这一点?

我在想

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

async Task ProcessAsynchronously(byte[] image)

其中ProcessX也被声明为async

这是一个好方法吗?

感谢您提供任何见解,因为我在异步处理方面的实践经验很少。

编辑包括评论中的所有建议,并增加了一些背景。

背景与见解

将一个函数转换为async不足以使整个过程保持非阻塞状态。 为了实现所需的功能,应将整个处理路径(调用堆栈)转换为非阻塞。 调用堆栈上的一种阻塞方法足以呈现整个进程阻塞。 如下面的一些示例所示, 非阻塞并不一定意味着async

将方法转换为async涉及两个步骤:

  • 更改签名以返回Task 这将使您的呼叫者可以跟踪方法的状态和结果。
  • async关键字添加到签名。 这将允许在您的方法主体中await其他async方法。

CPU绑定与IO绑定任务

请注意,即使在await方法时,也不一定意味着您立即将线程释放回调用方。 await的方法仅在依次开始await IO绑定操作时才将线程释放给您。 await -ed方法在首次await -IO绑定操作之前执行CPU绑定操作时,您的线程仍将阻塞。

例如:

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

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

另一方面,

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

await MyAsyncMethod(); // will return immediately

如果您有CPU限制的任务,但仍然不想阻止调用者线程,则可以采用以下解决方法:

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

您的情况该怎么办

由于我不确定为什么不能将OnFrameSampleAcquired签名更改为async ,因此我将建议几个不同的选项。

选项1

最简单,真正异步的方法是:

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);
    // ...
}

如果处理路径上的所有方法看起来都像这样,则说明您已正确实现了非阻塞后台作业。

选项2

如果您绝对无法更改签名OnFrameSampleAcquired,则有一种解决方法。 相反,您可以按照@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
}

在这里,您会赢得双赢:首先,您不必更改OnFrameSampleAcquired的签名; 其次,OnFrameSampleAcquired现在是一种非阻塞方法。

选项3

如果由于必须实现以下接口而无法更改签名:

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

那么您可以将async关键字添加到您的方法中,并且仍然符合该接口:

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

此选项的缺点是,调用者无法跟踪任务的状态(仍在运行还是已完成?),也无法跟踪其结果(已完成还是抛出异常?)。 您可能必须在try/catch包装OnFrameSampleAcquired的整个主体,并编写一个异常以进行日志记录。

选项4

从技术上讲,您还可以使用“ Wait Task从非异步OnFrameSampleAcquired异步调用异步ProcessAs,但这不会实现拥有非阻塞后台任务的目标 Wait()将阻塞线程,直到异步处理完成:

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

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

    //some more code here
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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