RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1250881
Accepted
Dezigl
Dezigl
Asked:2022-03-03 06:31:56 +0000 UTC2022-03-03 06:31:56 +0000 UTC 2022-03-03 06:31:56 +0000 UTC

.NET Framework 4.8 上 WPF 下的 C# 中的线程、任务、异步、等待

  • 772

我想@effetto从habr的一句话开始

线程(thread,thread)——是一个处理器线程的封装。这是对系统功能的软件封装。任务(task)——代表一个可以并行执行的工作单元。使用一种或另一种工具取决于您的任务。例如,如果您需要在后台执行操作,您的选择是线程。如果您需要并行执行计算或等待异步操作(例如,I/O),那么您的选择就是任务。

如果你相信他的话,那为什么不能用来Task在后台执行操作呢?毕竟,他们自己分散在溪流中,在后台做任务并带来答案。

进一步滚动并看到这篇文章:Task 和 Thread 之间有什么区别,什么时候使用哪个更好?,它@VladD说

Thread 代表一个物理的、系统的执行线程(除了 .NET 2.0 下的 SQL Server,是的)。而且Task本质上是从一个线程跳到另一个线程的东西,而且通常根本不在任何线程中!

对于当前版本的语言,优先选择任务并避免线程几乎总是有意义的,它们太低级了。使用任务,他们可以做得更多。

然后他说

无需手动完成任何事情,线程池将负责在内核之间分配负载。C# 是一种高级语言,系统相关的细节会在其中自动解决。

这里我有一个不和谐。事实Thread证明,它们没有以任何方式与核心连接,并且另一种机制分散了核心上的负载。那为什么Thread's 是必要的,如果有Thread Pool一个可以为你做所有事情的魔法,只需给Task's 进行处理。因此,除此之外,您Tread也被孤立了,无法从他们那里得到答案。

于是他们async坐await了下来。他们是什么,无法理解。带有 async 的代码看起来像伪多线程代码,但由于某种原因,它在不同的线程中执行。我不明白。

你能解释一下如何在你的头脑中建立一个层次结构或一个连接系统来理解Thread's, Task'and, asyncandawait吗?

这是我正在摸不着头脑的问题:有一个 wpf 应用程序有一个带有错误显示的界面。有一些循环功能需要在后台完成。如果某个函数出现问题,您需要将错误吐出到界面中并重新启动循环函数,或者运行另一个并行函数,或者其他什么。

Thread当 UI 挂在循环函数上时,有人可能会说,随意使用它作为多线程问题的临时解决方案。

public partial class MainWindow : Window
{
    MyProcessor p = new MyProcessor();
    public MainWindow()
    {
        InitializeComponent();
        p.Start();
    }
}

public class MyProcessor
{
    private bool somethingIsWrong;
    private Thread thread = null;
    public void Start()
    {
        Stop();
        thread = new Thread(Loop);
        thread.Start();
    }
    public void Stop()
    {
        if (thread != null && thread.IsAlive)
        {
            thread.Abort();
            thread = null;
        }
    }
    private void Loop()
    {
        while(true)
        {
            if (somethingIsWrong)
            {
                Stop();
                // Выплюнуть ошибку родителю (или в главный поток GUI)
            }
            // Do something
        }
    }
}

如何将此代码重写为功能Выплюнуть ошибку родителю?使用async/await?或者Task?

是否可以以简单的方式使用Task类似Thread'y 的东西?当我第一次发现它们时,我觉得这是可能的,但是当我开始深入挖掘时,结果发现它似乎不是......

也许Thread这些是 's 的某种容器Task?

c#
  • 2 2 个回答
  • 10 Views

2 个回答

  • Voted
  1. Best Answer
    VladD
    2022-03-03T07:07:32Z2022-03-03T07:07:32Z

    对于初学者来说,有系统线程(我称它们为线程,好吗?) - 这是操作系统的低级原语,是并行执行的代码。操作系统线程并行运行,它们使用例如进程的共享内存相互通信。

    现在,.NET 不提供对系统线程的直接访问*,而是定义了自己的。这些线程通常建立在系统线程之上(这不能保证,但在大多数情况下是这样)。

    为什么需要 .NET 线程?好吧,例如,.NET 不必在 Windows 平台上运行,如果每个系统都必须以自己的方式使用它们,那么编写跨平台代码将非常不方便。

    在任何情况下,系统线程和 .NET 线程都提供了非常简单的功能:与程序的其余部分并行运行给定的 void 函数,并阻塞等待该函数完成。

    为什么.NET 中有这样一个原语?事实上,历史上程序员都习惯于使用线程,所以不给他们这个机会是不好的,而且一开始并没有那么多其他的异步手段。

    接下来是线程池(thread pool)。这是.NET的一个特性(原生应用的Windows有自己的线程池,Windows下的.NET实现是否依赖,我不知道)。它包含一组内部已经运行的线程,并提供“在当前空闲的线程上执行给定的 void 函数”的功能。如果没有空闲线程,则作业排队。**

    它有什么用,难道不能用流来解决吗?事实上,启动一个新线程(尤其是在 Windows 上)是一项相当昂贵的操作,而池允许您避免这种情况。

    接下来,任务。这是一个完全不同的逻辑层次。任务是一个将永远被执行的任务***并产生一个值或抛出一个异常。任务如何执行并不重要:它可以作为另一个线程中的函数执行,它不能在任何地方执行,而只是等待某个事件发生(例如,数据通过网络到达),它可以在多个线程中同时执行,其中的结果将在稍后收集 - 任务可以是任何东西。

    任务****提供以下功能:

    • 完成任务后,报告结果或失败
    • 在异步函数中,等待给定任务或一组任务的异步执行,并将其结果或异常传递给等待点
    • 在正常的同步函数中,等待Task同步执行(但是,这种情况下建议使用异步函数)
    • 在线程池或新线程上将您的(不一定是 void-)函数作为任务运行。
    • 将其他异步模式(IAsyncResult、Begin + 完成事件、等待句柄)打包到 Task 中
    • 任务具有使用 CancellationTokens 的标准合作终止机制
    • 您可以将延续附加到任务 - 另一个任务将在此任务结束时执行(可选 - 在成功/失败/取消时)
    • 最后,最重要的是可以使用异步函数创建任务,这使得从可用任务和普通语言原语(如 for / while / if)构造其他更复杂的任务变得容易。

    如您所见,Task 在功能方面比线程丰富得多。

    现在,异步/等待。这些东西是这样工作的。async 关键字声明了一个生成任务的函数,并且可以在其内部使用 await 关键字异步等待其他任务。这样我们可以很容易地以最复杂的方式组合不同的任务。(这是一个例子。)

    此外,关于长后台任务。能够为这些相同的任务提供服务对您来说仍然是一件好事 - 等到它们完成,获得结果(至少成功与否),因此 Task 在这里也是一个自然的选择。如果您的长期工作实际上大部分时间都没有做任何事情(通常是这样),那么您在等待时就不会浪费宝贵的线程。如果你的后台任务一直很忙,那么为了不长时间从池中取出一个线程,你可以将它发送到一个新线程。

    好吧,我将在 WPF 中设计您的代码,如下所示。

    class MyProcessor
    {
        public Task Work(IProgress<ErrorDescription> progress, CancellationToken ct) =>
            Task.Run(WorkInternal(progress, ct));
    
        async Task WorkInternal(IProgress<ErrorDescription> progress, CancellationToken ct)
        {
            while (!ct.IsCancellationRequested)
            {
                try
                {
                    // что-то делать, для пауз использовать await Task.Delay(..., ct)
                }
                catch (OperationCancelledException ex) when (ex.CancellationToken == ct)
                {
                    // всё хорошо, от нас требуют выйти, выходим
                    break;
                }
                catch (другие возможные исключения)
                {
                    progress.Report(тут информация о том, что пошло не так);
                    // восстанавливаемся, чтобы на следующем цикле продолжить
                }
            }
        }
    }
    

    在 WPF 方面,您只需编写

    CancellationTokenSource cts = new();
    Task workTask = myProcessor.Work(
            cts.Token,
            new Progress<ErrorDescription>(errorInfo => { отобразить ошибку в UI }));
    

    带有 errorInfo 的回调将自动进入正确的线程。

    在工作结束时,不要忘记停止异步工作

    cts.Cancel();
    await workTask;
    

    *好吧,好吧,如果通过 P/Invoke 可以,但它是调用 OS 函数和其他非托管库的低级工具。

    **其实有点小技巧,线程池如果看到已经积累了未完成的任务就可以创建额外的线程,如果线程空闲没有工作就杀死线程。

    ***在其他语言中,这通常被称为未来或承诺。

    ****更具体地说,TPL 库和异步/等待功能。

    • 19
  2. aepot
    2022-03-03T07:25:54Z2022-03-03T07:25:54Z

    我会用简单的术语来尝试。

    Thread- 这个东西可以完成工作。你把密码喂给她,她就会咀嚼它。就像您的主代码被执行一样,包括为您创建和启动新线程的代码。一般来说,任何代码都在内部执行Thread,不能在其他任何地方执行。也就是说,它是代码执行所需的物理实体。当您运行多个线程以在单位时间内执行尽可能多的代码时,称为多线程。

    Task- 这东西给了你等待的能力。期望什么:例如,I/O 操作。例如,您发送了一个 TCP 请求并且正在等待。在您等待时,此代码是否在某个线程上运行?很有可能并且没有详细说明 - 肯定是的。但是这个线程不是在你的电脑上,而是在服务器上,你的电脑正在等待数据包的到达,以便给你数据,并从你发送数据包时它停止的地方开始处理方法。等待某些东西“在那里”运行称为异步。

    在您等待时,可能根本没有一个线程在任何地方运行。例如,硬盘控制器可能会寻找您需要为您提供数据的文件,在这个地方,处理器可能什么都不做,也不执行您的代码。换句话说,可以使用 0 到 100500 个线程来执行异步操作。一个大的异步操作可以由许多小的异步操作组成,对于每个小的异步操作,可以使用从 0 到多个线程,以此类推。

    你现在能期待什么。

    await MyShinyCoolMethodAsync();
    

    也许不是马上

    Task task = MyShinyCoolMethodAsync();
    Console.WriteLine("Оно уже запущено!");
    await task;
    Console.WriteLine("Оно наконец-то закончилось.");
    

    你甚至可以一次等待一堆正在运行的异步操作。

    List<Task> tasks = new List<Task>();
    for (int i = 0; i < 100500; i++);
    {
        tasks.Add(MyShinyCoolMethodAsync());
    }
    await Task.WhenAll(tasks);
    

    还有更多是可能的。也可以(但这并不准确)实现上面编写的这 4 行代码,但只能使用流(不是错误,但在抛出异常、返回结果和其他乐趣方面的行为完全相同async)。顺便说一句,我最多会编写 500 行代码,以便在裸的基础上制作类似的东西Thread。

    还有什么可以等待Task- 例如,当某些代码将在另一个线程中执行时。TPL(任务并行库)API 提供了在不同线程上轻松运行代码的能力。

    Task task = Task.Run(() => Console.WriteLine($"Привет из потока номер {Thread.CurrentThread.ManagedThreadId}"));
    

    也就是说,你可以猜到这个Task.Run在自己内部创建了什么Thread,然后把代码放在那里。非常正确。

    只是创建一个线程是一件昂贵的事情,它会吃掉计算机资源,而且为了省钱,线程并不是每次调用都启动,而是只在必要时重复使用。也就是说,你启动了 1 个方法——创建了一个线程,执行了代码,释放了线程。当你完成第一个方法后,启动第二个方法,那么之前创建的线程可以被重用,如果它是空闲的。这称为线程池。

    Task.Run默认情况下使用线程池,但可以对其进行调整以使其不使用,但会产生一个单独的私有线程,并且您将被迫等待约 30 毫秒直到发生这种情况(在执行期间会发生同样的事情Thread.Start(),同样的延迟) . 相比之下,从池中获取免费流的时间约为 1-2 毫秒。实际上,多线程性能不佳是线程池解决的主要问题。

    C# 中的异步代码可以用多种方式编写,但最简单的方式是使用两个关键字async/await。第一个从方法创建一个状态机(我不会咀嚼它,这个主题很容易谷歌),第二个允许您等待任务完成并将这个状态机从一个状态转换为另一个状态。也就是说,它继续执行看起来像一个完整的方法,但实际上它在你使用的地方被切割成碎片await。

    await没有async在方法中使用它是行不通的。好吧,如果没有机器本身,就没有办法改变状态机的状态。async不带也可以用,但是没用await,而且好像买了车,洗了车,加油了,哪儿也没去。“不酷,”你可能会想。因此,如果您在async没有await. 所以,只有在一起,没有别的。


    您的代码,异步。

    我将立即从构造函数中删除代码,因为它无法附加到它async,并移至事件处理程序Window.Loaded。

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    
        private async void Window_Loaded(object sender, RoutedEventArgs e)
        {
            try
            {
                MyProcessor p = new MyProcessor();
                await p.StartAsync();
            }
            catch (SomethingWentWrongException ex)
            {
                MessageBox.Show($"Ура! {ex.Message}");
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Всё сломалось: {ex.Message}");
            }
        }
    }
    
    public class MyProcessor
    {
        private bool somethingIsWrong;
        private CancellationTokenSource cts;
    
        public async Task StartAsync()
        {
            if (cts != null)
                return;
    
            try
            {
                using (cts = new CancellationTokenSource())
                {
                    await LoopAsync(cts.Token);
                }
            }
            finally
            {
                cts = null;
            }
        }
    
        public void Stop()
        {
            cts?.Cancel();
        }
    
        private async Task LoopAsync(CancellationToken token)
        {
            while (!token.IsCancellationRequested)
            {
                await Task.Delay(100500, token); // почти все методы умеют принимать токен отмены
                if (somethingIsWrong)
                {
                    throw new SomethingWentWrongException("Что-то пошло не так");
                }
                // Do something
    
                token.ThrowIfCancellationRequested(); // вместо проверки IsCancellationRequested можно бросать исключение, если отмена
            }
        }
    }
    

    文档。

    • 7

相关问题

  • 使用嵌套类导出 xml 文件

  • 分层数据模板 [WPF]

  • 如何在 WPF 中为 ListView 手动创建列?

  • 在 2D 空间中,Collider 2D 挂在玩家身上,它对敌人的重量相同,我需要它这样当它们碰撞时,它们不会飞向不同的方向。统一

  • 如何在 c# 中使用 python 神经网络来创建语音合成?

  • 如何知道类中的方法是否属于接口?

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    表格填充不起作用

    • 2 个回答
  • Marko Smith

    提示 50/50,有两个,其中一个是正确的

    • 1 个回答
  • Marko Smith

    在 PyQt5 中停止进程

    • 1 个回答
  • Marko Smith

    我的脚本不工作

    • 1 个回答
  • Marko Smith

    在文本文件中写入和读取列表

    • 2 个回答
  • Marko Smith

    如何像屏幕截图中那样并排排列这些块?

    • 1 个回答
  • Marko Smith

    确定文本文件中每一行的字符数

    • 2 个回答
  • Marko Smith

    将接口对象传递给 JAVA 构造函数

    • 1 个回答
  • Marko Smith

    正确更新数据库中的数据

    • 1 个回答
  • Marko Smith

    Python解析不是css

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5