将第三方API调用转换为Async

时间:2013-11-13 17:31:25

标签: c# task-parallel-library async-await

我正在左右阅读有关异步和任务但我仍然不知道如何将现有的第三方调用包装成异步方法。

主要是第三方dll提供了一种调用其API之一的方法

var client = new FooClient();
var response = client.CallMethod(param1, message);

if (response.RestException != null)
{
    status = response.RestException.Message;
    return false;
}
else 
   return true

并且此预期的调用将阻止当前线程,直到它返回响应。

所以我查看了Task和其他方法来调用Async但仍然得到响应的原因,因为应用程序将采取不同的操作。

我觉得任务是要走的路,但他们的DLL没有CallMethodAsSync方法,所以我不知道该做什么。

任何帮助都将不胜感激!!

1 个答案:

答案 0 :(得分:5)

最简单的方法是将调用转储到Task.Run中,然后等待返回Task.Run。这将把密集的工作卸载到另一个线程,允许UI线程继续。这是一个方法的简单示例,它必须等待另一个时间密集型方法才能返回:

    static void Main()
    {
        Console.WriteLine("Begin");
        var result = BlockingMethod("Hi!");
        Console.WriteLine("Result: " + result);
        Console.ReadLine();
    }

    static bool BlockingMethod(string someText)
    {
        Thread.Sleep(2000);
        return someText.Contains("SomeOtherText");
    }

正如我们所看到的,BlockingMethod方法有一个Thread.Sleep(2000)作为它的第一个语句。这意味着运行调用方法Main的线程必须等待整整两秒才能获得BlockingMethod的结果。如果运行Main方法的线程正在处理UI重绘,那么这意味着我们得到的UI看起来没有响应/锁定整整两秒钟。我们可以将等待BlockingMethod的工作卸载到另一个线程上,如下所示:

首先我们将调用方法标记为'async',因为这会告诉编译器为async方法中的所有'await'生成类似状态机的内容:

    static async void Main()
    {
        Console.WriteLine("Begin");
        var result = BlockingMethod("Hi!");
        Console.WriteLine("Result: " + result);
        Console.ReadLine();
    }

接下来,我们将返回类型'void'转换为Task。这允许其他线程等待它(如果您不关心它,可以将其保留为async void,但具有返回类型的异步方法需要返回Task):

    static async Task Main()
    {
        Console.WriteLine("Begin");
        var result = BlockingMethod("Hi!");
        Console.WriteLine("Result: " + result);
        Console.ReadLine();
    }

现在允许我们的方法异步运行,并允许其他方法等待它。但这还没有解决我们的问题,因为我们仍在同步等待阻塞方法。因此,我们通过调用Task.Run将阻塞方法移到另一个线程,并等待其结果:

    static async Task Main()
    {
        Console.WriteLine("Begin");
        await Task.Run(() => BlockingMethod("Hi!"));
        Console.WriteLine("Result: " + result);
        Console.ReadLine();
    }

但这给我们带来了一个问题:我们的阻塞方法返回了我们稍后想要在我们的方法中使用的东西,但Task.Run返回void!为了访问在另一个线程中运行的阻塞方法返回的变量,我们必须在闭包中捕获局部变量:

    static async Task Main()
    {
        Console.WriteLine("Begin");
        bool result = true; 
        await Task.Run(() => result = BlockingMethod("Hi!"));
        Console.WriteLine("Result: " + result);
        Console.ReadLine();
    }

因此,总而言之,我们所做的是我们采用了一种正常的同步方法(我们可以重写的调用方法,而不是我们无法重写的第三方API),我们更改了它以便将其标记为async并返回了一个Task。如果我们希望它返回结果,那么它需要是一般类型的任务。之后,我们在Task.Run中调用了阻塞方法 - 它为阻塞方法创建了一个新线程 - 然后我们给它一个Action(用lambda语法)来运行。在该Action中,我们引用了一个在调用函数中定义的变量(这是闭包),它可以捕获阻塞方法的结果。

现在,我们异步地在其他地方等待同步阻塞方法。阻塞方法本质上不是异步的并不重要,因为我们让它在另一个线程中同步运行,并允许我们的线程等待它的结果。

如果其中任何一项不清楚,请发表评论。 Asynchrony起初有点令人困惑,但它对于响应式UI来说是天赐之物。

编辑:

在回应Scott Chamberlain的评论时,Task.Run还有一个重载,它返回运行它的方法的类型(提供一个Func,而不是一个Action)。所以我们可以简单地使用:

    static async Task MainAsync()
    {
        Console.WriteLine("Begin");
        bool result = await Task.Run(() => BlockingMethod("Hi!"));
        Console.WriteLine("Result: " + result);
        Console.ReadLine();
    }

请记住 - 正如Scott Chamberlain所指出的那样 - 在另一个线程中运行工作没有固有的性能优势。在许多情况下,它实际上可能导致更差的性能,因为设置和拆除线程是昂贵的。基于任务的异步仅对于保持忙线程(例如UI线程)未被阻塞是有用的,因此它可以响应请求,或者当通过使用例如正确地对工作进行细分时。 Parallel.ForEach。