了解异步代码:在C#和Java中的异步编程的比较
#java #csharp #async

在此博客文章中,我们将讨论异步代码是什么,并探讨其执行方式如何在不同的语言中变化。我们将使用C#和Java说明不同的方法。

介绍

让我们的成像我们正在进行平坦的翻新。我们想油漆墙壁并安装新的地板,但是在我们这样做之前,我们需要下订单涂上油漆和地板,等待其交付并取下所有家具,以免损坏它在此过程中。

如果我们要编写一个为我们执行的程序,那么我们可以在 asynchronous 同步执行之间进行选择。

如果我们同步执行此操作,则订单将如下:

  1. 第0天:放置并订购油漆并等待几天直到交付(2天)。
  2. 第2天:下订单订购木地板,然后等到交付(20天 - 交货缓慢)。
  3. 第22天:从您的公寓(5天)中删除所有家具。
  4. 第27天:画墙(5天)。
  5. 第32天:安装木地板(5天)。
  6. 第37天:您翻新完成了:)总共花费了37天。

这听起来不有效。当然,我们可以在不等待涂料的情况下下订单木地板。或者,我们可以在交货到达之前开始删除家具。换句话说,我们可以进行许多操作异步

序列看起来像这样:

  1. 第0天:下订单(交货将需要2天)。
  2. 第0天:下订单木地板(交货将需要20天)。
  3. 第0天:拆除家具(5天)。
  4. 第5天:油漆已经到3天前到达了 - 让S绘画墙(5天)。
  5. 第10天:仍在等待10天,直到地板交付 - 我们现在没有任何事情要做(10天)。
  6. 第20天:地板终于到了 - 让我们安装它(5天)。
  7. 第25天:AAAND完成,总共花了25天。

更正式的是,在异步执行模型(也称为非阻止)中,您可以启动任务,但不一定要等待其完成,直到实际需要结果为止。在同步模型(也称为阻止)中,您依次执行所有操作。

这是对同步执行模型和异步执行模型的常见介绍。编程语言以不同的方式实现此模型。

我们将探讨如何在C#和Java中实现此示例并比较其模型。

异步执行模型

异步执行并不意味着每个异步操作产生或使用单独的线程 - 尽管多线程和异步性密切相关,但这些都是独立的概念。例如,消息队列队列是对处理任务的设计模式之一 - 它可以使用一个或多个线程(消息将从单独的线程提交)。

在C#中,根据Microsoft documentation,异步 /等待关键字不会导致创建额外的线程,因为异步方法不会在其自己的专用线程上执行。取而代之的是,该方法在当前同步上下文中运行,并仅在主动处理线程时才能使用线程的时间。同步上下文表示代码正在运行的执行上下文,并且在某些示例中是single threaded

如果同步上下文为null(如下面的控制台应用程序示例所示),则异步操作将使用线程池进行执行。如果方法到达await关键字并产生执行,则其延续不一定使用相同的线程。

在Java中,有多种编写异步代码的方法。我们只会在此Blogpost( Note )中讨论CompletableFuture:我们将无法作为预览功能提供的封面虚拟线程)。 Java使用单独的线程执行异步任务,并且不会在JVM级别上切换上下文。即使您创建了一个螺纹执行程序(Executors.newSingleThreadExecutor()),它仍然是第二个线程(除了主线程之外),如果要求等待执行结果,则将阻止主线程。

node.js中使用了另一个模型,它利用事件循环进行异步操作。您可以在this文章中阅读有关它的信息。

让我们现在在C#和Java中看到一些示例。

C#示例

如引言中解释:
中解释的异步范式中的介绍从我们的简介中进行翻新。

internal class Renovation
{
    static int Id => Thread.CurrentThread.ManagedThreadId;
    static async Task BuyPaint()
    {
        Console.WriteLine($"{Id} Placing an order for paint at a local shop - it'll be delivered quickly.");
        await Task.Delay(TimeSpan.FromSeconds(2));
        Console.WriteLine($"{Id} Paint delivered!");
    }

    static async Task BuyWoodenFloor()
    {
        Console.WriteLine($"{Id} Placing an order for wooden floor. The delivery will take over a month.");
        await Task.Delay(TimeSpan.FromSeconds(20));
        Console.WriteLine($"{Id} Wooden floor is finally delivered!");
    }

    static async Task RemoveFurniture()
    {
        Console.WriteLine($"{Id} Starting to remove the furniture... This will take a few days.");
        await Task.Delay(TimeSpan.FromSeconds(5));
        Console.WriteLine($"{Id} Removed all furniture from the room!");
    }

    static async Task PaintingTheWalls()
    {
        Console.WriteLine($"{Id} Now let's paint the walls - should be fairly quick.");
        await Task.Delay(TimeSpan.FromSeconds(5));
        Console.WriteLine($"{Id} Walls are painted!");
    }

    static async Task InstallNewFloor()
    {
        Console.WriteLine($"{Id} Let's install the floor.");
        await Task.Delay(TimeSpan.FromSeconds(5));
        Console.WriteLine($"{Id} Floors are installed!");
    }

    static async Task Main(string[] args)
    {
        Console.WriteLine($"Is Background thread? {Thread.CurrentThread.IsBackground}");
        Console.WriteLine($"Is SynchronizationContext null? {System.Threading.SynchronizationContext.Current == null}");

        Task paint = BuyPaint();
        Task woodenFloor = BuyWoodenFloor();
        Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} Placed all orders! Let's remove all the furniture from the room...");
        await RemoveFurniture();

        await paint;
        await PaintingTheWalls();

        await woodenFloor;
        await InstallNewFloor();

        Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} The renovation is done :)");
        Console.WriteLine($"Is Background thread? {Thread.CurrentThread.IsBackground}");
    }
}

这是输出:

PS C:\Users\darya\VSCode> dotnet run
**Is Background thread?** False
Is SynchronizationContext null? True
1 Placing an order for paint at a local shop - it'll be delivered quickly.
1 Placing an order for wooden floor. The delivery will take over a month.
1 Placed all orders! Let's remove all the furniture from the room...
1 Starting to remove the furniture... This will take a few days.
5 Paint delivered!
5 Removed all furniture from the room!
5 Now let's paint the walls - should be fairly quick.
5 Walls are painted!
5 Wooden floor is finally delivered!
5 Let's install the floor.
5 Floors are installed!
5 The renovation is done :)
**Is Background thread?** True

有一些有趣的事情要注意:

  • c#在遇到等待关键字之前,永远不会产生对执行的控制。例如。它首先记录了它为绘画和屈服下的订单,然后将其记录在木地板上并屈服,然后才开始去除家具。
  • 如上所述,它们同步上下文为null,因此代码使用线程池。在峰值,三种方法并联 - 主要,Buypaint和Buywoodenfloor。 Buypaint始于线程#1,但是当它恢复时,它使用线程#5,然后在此线程上继续执行主方法。
  • 主方法在主线程上启动,但是在几个异步调用后,它将在末端的背景线程上执行。

Java示例

现在使用CompletableFuture在Java中实现同一示例:

public class Main {
    static int getId() {
        return (int) Thread.currentThread().threadId();
    }

    static void buyPaint() {
        try {
            System.out.println(getId() + " Placing an order for paint at a local shop - it'll be delivered quickly.");
            TimeUnit.SECONDS.sleep(2);
            System.out.println(getId() + " Paint delivered!");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    static void buyWoodenFloor() {
        try {
            System.out.println(getId() + " Placing an order for wooden floor. The delivery will take over a month.");
            TimeUnit.SECONDS.sleep(20);
            System.out.println(getId() + " Wooden floor is finally delivered!");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    static void removeFurniture() {
        try {
            System.out.println(getId() + " Starting to remove the furniture... This will take a few days.");
            TimeUnit.SECONDS.sleep(5);
            System.out.println(getId() + " Removed all furniture from the room!");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    static void paintingTheWalls() {
        try {
            System.out.println(getId() + " Now let's paint the walls - should be fairly quick.");
            TimeUnit.SECONDS.sleep(5);
            System.out.println(getId() + " Walls are painted!");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    static void installNewFloor() {
        try {
            System.out.println(getId() + " Let's install the floor.");
            TimeUnit.SECONDS.sleep(5);
            System.out.println(getId() + " Floors are installed!");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        Executor executor = Executors.newFixedThreadPool(/* nThreads= */ 3);
        CompletableFuture<Void> paint = CompletableFuture.runAsync(Main::buyPaint, executor);
        CompletableFuture<Void> woodenFloor = CompletableFuture.runAsync(Main::buyWoodenFloor, executor);

        System.out.println(getId() + " Placed all orders! Let's remove all the furniture from the room...");

        removeFurniture();

        paint.join();
        CompletableFuture.runAsync(Main::paintingTheWalls, executor).join();

        woodenFloor.join();
        CompletableFuture.runAsync(Main::installNewFloor, executor).join();

        System.out.println(getId() + " The renovation is done :)");
    }
}

和输出:

23 Placing an order for paint at a local shop - it'll be delivered quickly.
24 Placing an order for wooden floor. The delivery will take over a month.
1 Placed all orders! Let's remove all the furniture from the room...
1 Starting to remove the furniture... This will take a few days.
23 Paint delivered!
1 Removed all furniture from the room!
25 Now let's paint the walls - should be fairly quick.
25 Walls are painted!
24 Wooden floor is finally delivered!
23 Let's install the floor.
23 Floors are installed!
1 The renovation is done :)
  • 我们看到java s CompletableFuture使用了我们为每种方法定义的线程池的单独线程。
  • 与C#不同,异步方法总是从头到尾在单独的线程上完全执行,并且不会产生执行。
  • 即使被阻止,主线程也总是被占据 - 主方法总是在主线程上启动和结束。

结论

异步代码是一个强大的模型,可以在正确使用时提高应用程序的响应能力。我们只是研究了它在各种语言中的工作方式不同。这是一个巨大的话题,我不会尝试在一个博客文章中介绍所有细节,但我希望它提供的证明表明,根据编程语言,看似相同的代码可以非常不同。


darya shirokova是一名软件工程师 @ google