8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

当多个 C# 任务在另一个任务完成时启动时会发生什么?

blogofsongs 2月前

68 0

假设我有以下代码:public async void Run(){ TaskCompletionSource t = new TaskCompletionSource(); Prepare(t.Task); await Task.Delay(1000); t.SetResult(); Console.

假设我有这样的代码:

public async void Run()
{
    TaskCompletionSource t = new TaskCompletionSource();
    Prepare(t.Task);
    await Task.Delay(1000);

    t.SetResult();
    Console.WriteLine("End");
}

public async void Prepare(Task task)
{
    await Run(task, "A");
    await Run(task, "B");
    await Run(task, "C");
    await Run(task, "D");
}

public async Task Run(Task requisite, string text)
{
    await requisite;
    Console.WriteLine(text);
}

时会发生什么 t.SetResult(); ?这是多线程吗?控制台中项目的顺序有任何保证吗?如果我有一个 List<> ,并且该 Run 方法会改变它,我需要担心多线程吗?

帖子版权声明 1、本帖标题:当多个 C# 任务在另一个任务完成时启动时会发生什么?
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由blogofsongs在本站《multithreading》版块原创发布, 转载请注明出处!
最新回复 (0)
  • 我想使用 SDL2 创建 ASCII 艺术,但我不明白为什么我会得到这个结果:原始图像的程序输出](https://i.sstatic.net/1IUV743L.png)是](https://i.sstatic.net/ytrb2...

    我想使用 SDL2 创建 ASCII 艺术,但我不明白为什么我会得到这个结果: 原始图像的 程序输出 https://i.sstatic.net/1IUV743L.png https://i.sstatic.net/1IUV743L.png ) ]( https://i.sstatic.net/ytrb2R0w.png ) 它是一个 bmp 文件。看起来图像被复制了,下面有一个巨大的空白。但是,我的代码似乎是正确的。

    这是在控制台中绘制图像的代码

    #define ASCII_WIDTH 130
    #define ASCII_HEIGHT 100
    
    int asciiArt(SDL_Surface* image)
    {
        if(image == NULL)
            return 0;
    
        SDL_Surface *imgResized = SDL_CreateRGBSurface(0, ASCII_WIDTH, ASCII_HEIGHT, 32, 0, 0, 0, 0);
        if(imgResized == NULL)
            return 0;
    
        SDL_BlitScaled(image, NULL, imgResized, NULL);
    
        char ascii[] = " `.-':_,^=;><+!rc*/z?sLTv)J7(|Fi{C}fI31tlu[neoZ5Yxjya]2ESwqkP6h9d4VpOGbUAKXHm8RD#$Bg0MNWQ%&@";
        Uint32* pixel = NULL;
        SDL_Color color;
        Uint8 gray;
        int pos;
    
        SDL_LockSurface(imgResized);
    
        for(int i = 0; i < imgResized->h; i++)
        {
            for(int j = 0; j < imgResized->w; j++)
            {
                pixel = (Uint32*)imgResized->pixels + i * imgResized->pitch + j * imgResized->format->BytesPerPixel;
                SDL_GetRGB(*pixel, imgResized->format, &color.r, &color.g, &color.b);
                gray = (color.r + color.g + color.b) / 3;
    
                pos = gray * (strlen(ascii) - 1) / 255;
    
                printf("%c", ascii[pos]);
            }
            printf("\n");
        }
    
        SDL_UnlockSurface(imgResized);
    
        if(imgResized != NULL)
            SDL_FreeSurface(imgResized);
    
        return 1;
    }
    

    我很迷茫,不知道错误在哪里,也不知道从哪里开始查找。谢谢!

  • 这是对一个真实问题的简化...如果我们关注的是答案而不是解决方法,我会很感激...

    1. SetResult() 内部 await 恢复执行 Run(Task, string) 并打印\'A\'。然后返回到 Prepare() await Run(task, "B") 将打印\'B\'。依此类推。最后将按顺序打印\'ABCD\'。
    2. 您无法确定它是否是多线程的。这取决于 TaskScheduler 当前环境中使用的内容以及在此调度程序上执行的其他任务。默认情况下,您将拥有一个包含多个线程的线程池。最有可能(但不保证)您的任务将在不同的线程上执行,但不会并行执行(如果我们谈论的是 ABCD 任务)。
    3. ABCD 的顺序是有保证的,因为您 await 在开始下一个任务之前先完成当前任务。
    4. 关于修改 List<> 内部 Run - 这很可能可行,因为没有 并发 编辑。同时这绝对是脆弱的代码,在未来的代码修改过程中可能会轻易被悄悄破坏。我将尽量避免这样的代码,并使用 locks 或一些并发集合。或者至少写一个长注释来描述当前实现的局限性。
  • 谢谢!我觉得我真的不能用任务来解决我的问题……我有一些并行任务,需要快速同步……我想我需要手动控制将方法分割成许多较小的方法,并手动控制它们的执行……

  • 您是否明白,当您向指针添加一个整数时,该整数会自动乘以指向类型的大小(在本例中为 4)?

  • @Everton 这就是为什么最好解释一下你想要实现的目标,而不是询问与你尝试的解决方案相关的细节。当然,你可以把它展示为你尝试过的东西,但如果我们确切地知道你在做什么,我们可能会提出一个解决方案。

  • 从技术上讲,如果不强制要求并行运行所有任务(由于硬件限制,这几乎是不可能的),那么您可以使用 Task.WhenAll() 和相关函数来实现一定的并行性,而不是连续等待。因此,您可以运行一堆任务,然后等待所有任务。在这种情况下,您将获得尽可能多的并行性(由线程池根据系统资源进行管理)。

  • gsm 2月前 0 只看Ta
    引用 9

    @EvertonElvioKoser 考虑接受这个答案并在下次提出更有针对性/更具体的问题......

  • 这在很大程度上取决于应用程序处理的吞吐量 - 如果流量很大,则可能涉及多个线程。

    但一般来说, Task 使用 async / await 并不保证多线程,当需要时才会使用新线程(来自线程池) - 当前线程正忙于某些工作并且无法承担新的工作。

    异步执行的想法是,当有工作需要等待时(我们的处理器必须等待某些事情,比如 I/O 操作),它会释放当前线程并让其执行其他工作。

    这样,一个线程可能正在“并行”执行两个任务。但这是多任务,而不是多线程。

    有了这些知识,让我们来分析您的代码。

    首先,您创建 TaskCompleti完成Source 具有 Task “正在进行”并且可以等待的 - 但是一旦我们调用它,它就会 SetResult on TaskCompletionSource .

    然后我们调用 Prepare ,我们并不等待,但它等待传递的 Task ,因此此执行路径将停止 await Run(task, "A") 并等待任务完成,这将在我们调用时发生 SetResult (如前所述)。之后 Prepare (因此任务已经在等待中),我们调用 await Task.Delay - 我们这里还有另一个 await ,这次是为了延迟。由于我们使用 Task s ,因此不会创建新线程,因为 await Run 只是等待,线程可以继续进行其他工作并将处理代码的执行。因此,等待延迟和等待任务将并行、异步完成。

    然后,当延迟完成时,它将设置任务的结果,这意味着 await Run(task, "A") 将获得\'解除阻塞\',打印结果并继续 await Run(task, "B") ,但这将尝试等待已经完成的任务,因此它将同步执行(任务已经完成,没有什么可等待的),立即打印\'B\',然后\'C\',然后\'D\'(按顺序)。

    您可能会遇到一个问题 - 您的程序可能会在 Prepare 方法中的所有任务完成之前完成 - 因为它是 async void ,我们无法等待它。我建议进行一些小改进:

    public async void Run()
    {
        TaskCompletionSource t = new TaskCompletionSource();
        var prepareTask = Prepare(t.Task);
        await Task.Delay(1000);
    
        t.SetResult();
        
        await prepareTask;
        
        Console.WriteLine("End");
    }
    
    public async Task Prepare(Task task)
    {
        await Run(task, "A");
        await Run(task, "B");
        await Run(task, "C");
        await Run(task, "D");
    }
    
    public async Task Run(Task requisite, string text)
    {
        await requisite;
        Console.WriteLine(text);
    }
    
  • 当你写了一个简单的 await 语句时;

    await requisite;
    

    C# 编译器将您的方法移至单独的类,并将该“简单”转换 await 为等效的内容;

    private Task requisite;
    private int state = 0;
    private void MoveNext()
    {
        switch (state)
        {
            case 0:
                var awaiter = requisite.GetAwaiter();
                if (!awaiter.IsCompleted)
                {
                    state = 1;
                    awaiter.OnCompleted(MoveNext);
                    return;
                }
                goto case 1;
            case 1:
                // resume execution here when the task is complete
                break;
        }
    }
    

    如您所见,如果任务已完成,您的代码将同步继续。如果任务未完成,则会注册一个回调,以便稍后继续执行。

    每个等待程序的具体实现可能完全不同。例如, Task.Yield 将返回一个永远不会完成的等待程序。强制继续在线程池上执行。

    大多数任务类型以及示例中的所有任务都会在 .SetResult 调用时同步调用每个延续方法。

    回到你的具体例子;

    public async void Prepare(Task task)
    {
        await Run(task, "A");
        await Run(task, "B");
        await Run(task, "C");
        await Run(task, "D");
    }
    

    第一次调用时, Run 任务未完成,因此 Run 将注册一个延续 Action 中的 await 第一个 Prepare 也将看到返回的任务未完成。尽管该方法 void 如此,但不会返回另一个未完成的任务。

    当您在延迟后恢复并调用 时 t.SetResult(); 将首先调用 Run 已注册的用于恢复 的延续方法 Run 方法完成时,而不是返回, .SetResult 将在未完成的任务上调用 ,从而立即调用 的延续 Prepare 。所有这些都将在同一线程中发生,在 t.SetResult 返回之前。

    Run 中放置断点 Prepare 并检查调用堆栈,您会看到线程堆栈与您通常预期的相反。每个返回 async 在堆栈中 SetResult 调用

  • MBo 2月前 0 只看Ta
    引用 12

    \'Task.Yield 将返回一个永远不会完成的等待程序。强制继续在线程池上执行。\'——实际上,await Task.Yield() 在捕获的 SynchronizationContext 上继续。这是其记录的行为。在我看来,这不是一个适合这个问题的例子。

  • 重点只是等待者可以有不同的行为。例如 I/O 和其他操作系统句柄和计时器。但大多数其他任务只是立即执行回调。

返回
作者最近主题: