จะประกาศภารกิจที่ยังไม่เริ่มต้นซึ่งจะรองานอื่นได้อย่างไร


9

ฉันทำการทดสอบหน่วยนี้แล้วและฉันไม่เข้าใจว่าทำไม "รอ Task.Delay ()" ไม่รอ!

   [TestMethod]
    public async Task SimpleTest()
    {
        bool isOK = false;
        Task myTask = new Task(async () =>
        {
            Console.WriteLine("Task.BeforeDelay");
            await Task.Delay(1000);
            Console.WriteLine("Task.AfterDelay");
            isOK = true;
            Console.WriteLine("Task.Ended");
        });
        Console.WriteLine("Main.BeforeStart");
        myTask.Start();
        Console.WriteLine("Main.AfterStart");
        await myTask;
        Console.WriteLine("Main.AfterAwait");
        Assert.IsTrue(isOK, "OK");
    }

นี่คือผลลัพธ์ของการทดสอบหน่วย:

หน่วยทดสอบผลลัพธ์

สิ่งนี้เป็นไปได้อย่างไร "รอ" ไม่รอและเธรดหลักยังคงดำเนินต่อไป?


ไม่มีความชัดเจนในสิ่งที่คุณพยายามจะทำ คุณช่วยเพิ่มผลผลิตที่คาดหวังได้ไหม
OlegI

1
วิธีการทดสอบมีความชัดเจนมาก - isOK คาดว่าจะเป็นจริง
Sir Rufo

ไม่มีเหตุผลในการสร้างงานที่ไม่ได้เริ่มต้น งานไม่ใช่เธรด แต่ใช้เธรด คุณพยายามจะทำอะไร? ทำไมไม่ใช้Task.Run()หลังจากแรกConsole.WriteLine?
Panagiotis Kanavos

1
@ คุณเพียงแค่อธิบาย threadpools คุณไม่จำเป็นต้องมีกลุ่มของงานเพื่อใช้คิวงาน คุณต้องการคิวงานเช่นวัตถุ Action <T>
Panagiotis Kanavos

2
@Elo สิ่งที่คุณพยายามทำมีอยู่แล้วใน. NET เช่นผ่านคลาส TPL Dataflow เช่น ActionBlock หรือคลาส System.Threading.Channels ที่ใหม่กว่า คุณสามารถสร้าง ActionBlock เพื่อรับและประมวลผลข้อความโดยใช้งานพร้อมกันตั้งแต่หนึ่งงานขึ้นไป บล็อกทั้งหมดมีบัฟเฟอร์อินพุตที่มีความจุที่กำหนดค่าได้ DOP และความจุช่วยให้คุณสามารถควบคุมการทำงานพร้อมกันคำขอคันเร่งและใช้งาน backpressure - หากมีการรอคิวข้อความมากเกินไปโปรดิวเซอร์รอ
Panagiotis Kanavos

คำตอบ:


8

new Task(async () =>

งานที่ไม่ได้ใช้แต่Func<Task> Actionมันจะเรียกวิธีการแบบอะซิงโครนัสของคุณและคาดว่ามันจะจบเมื่อมันกลับมา แต่มันไม่ได้ ส่งคืนภารกิจ งานนั้นไม่ได้ถูกรอคอยโดยงานใหม่ สำหรับงานใหม่งานจะเสร็จสิ้นเมื่อวิธีที่ส่งคืน

คุณต้องใช้งานที่มีอยู่แล้วแทนที่จะห่อในงานใหม่:

[TestMethod]
public async Task SimpleTest()
{
    bool isOK = false;

    Func<Task> asyncMethod = async () =>
    {
        Console.WriteLine("Task.BeforeDelay");
        await Task.Delay(1000);
        Console.WriteLine("Task.AfterDelay");
        isOK = true;
        Console.WriteLine("Task.Ended");
    };

    Console.WriteLine("Main.BeforeStart");
    Task myTask = asyncMethod();

    Console.WriteLine("Main.AfterStart");

    await myTask;
    Console.WriteLine("Main.AfterAwait");
    Assert.IsTrue(isOK, "OK");
}

4
Task.Run(async() => ... )เป็นตัวเลือกด้วย
Sir Rufo

คุณเพิ่งทำเช่นเดียวกับผู้เขียนคำถาม
OlegI

BTW myTask.Start();จะยกระดับInvalidOperationException
เซอร์ Rufo

@OlegI ฉันไม่เห็นมัน คุณช่วยอธิบายได้มั้ย
nvoigt

@nvoigt ฉันคิดว่าเขาหมายถึงจะให้ผลผลิตมีข้อยกเว้นสำหรับทางเลือกของเขาและควรจะออกเมื่อใช้myTask.Start() Task.Run(...)ในการแก้ปัญหาของคุณไม่มีข้อผิดพลาด
404

3

ปัญหาคือคุณใช้Taskคลาสที่ไม่ใช่แบบทั่วไปซึ่งไม่ได้หมายถึงการให้ผลลัพธ์ ดังนั้นเมื่อคุณสร้างTaskอินสแตนซ์ผ่านตัวแทน async:

Task myTask = new Task(async () =>

... async voidผู้ร่วมประชุมจะถือว่าเป็น async voidไม่ได้เป็นTaskก็ไม่สามารถรอคอยข้อยกเว้นที่ไม่สามารถจัดการได้และเป็นแหล่งที่มาของพันของคำถามที่ทำโดยโปรแกรมเมอร์ผิดหวังที่นี่ใน StackOverflow และที่อื่น ๆ การแก้ปัญหาคือการใช้งานทั่วไปชั้นเพราะคุณต้องการที่จะกลับผลและผลที่ได้ก็เป็นอีกหนึ่งTask<TResult> Taskดังนั้นคุณต้องสร้างTask<Task>:

Task<Task> myTask = new Task<Task>(async () =>

ตอนนี้เมื่อคุณStartด้านนอกก็จะแล้วเสร็จเกือบจะทันทีเพราะงานของตนเป็นเพียงการสร้างภายในTask<Task> Taskจากนั้นคุณจะต้องรอด้านในTaskเช่นกัน นี่คือวิธีที่สามารถทำได้:

myTask.Start();
Task myInnerTask = await myTask;
await myInnerTask;

คุณมีสองทางเลือก หากคุณไม่ต้องการการอ้างอิงที่ชัดเจนไปยังด้านในTaskคุณสามารถรอด้านนอกTask<Task>สองครั้ง:

await await myTask;

... หรือคุณสามารถใช้วิธีการขยายในตัวUnwrapที่รวมงานด้านนอกและงานด้านในเป็นหนึ่งเดียว:

await myTask.Unwrap();

การคลายออกจะเกิดขึ้นโดยอัตโนมัติเมื่อคุณใช้Task.Runวิธีการยอดนิยมที่สร้างงานร้อนแรงขึ้นดังนั้นจึงUnwrapไม่ได้มีการใช้บ่อยมากในปัจจุบัน

ในกรณีที่คุณตัดสินใจว่าผู้ร่วมประชุม async ของคุณจะต้องกลับมาส่งผลให้เช่นstringนั้นคุณควรประกาศตัวแปรเป็นชนิดmyTaskTask<Task<string>>

หมายเหตุ:ฉันไม่รับรองการใช้ตัวTaskสร้างสำหรับการสร้างงานเย็น โดยทั่วไปแล้วการทำหน้านิ่วคิ้วขมวดด้วยเหตุผลที่ฉันไม่รู้จริง ๆ แต่อาจเป็นเพราะมีการใช้บ่อยครั้งจนมีโอกาสจับผู้ใช้ / ผู้ดูแล / ผู้ตรวจทานคนอื่น ๆ ที่ไม่รู้ตัวของโค้ดด้วยความประหลาดใจ

คำแนะนำทั่วไป:ระวังทุกครั้งที่คุณส่งตัวแทน async ให้เป็นวิธีการ วิธีนี้ควรคาดว่าจะFunc<Task>โต้แย้ง (หมายถึงเข้าใจตัวแทน async) หรืออย่างน้อยFunc<T>อาร์กิวเมนต์ (หมายความว่าอย่างน้อยสร้างTaskจะไม่ถูกละเว้น) ในกรณีที่โชคร้ายที่วิธีการนี้ยอมรับตัวแทนของคุณเป็นไปที่จะถือว่าเป็นAction async voidนี่เป็นสิ่งที่คุณต้องการไม่บ่อยนัก


ช่างเป็นคำตอบทางเทคนิคที่ยอดเยี่ยมแค่ไหน! ขอบคุณ.
Elo

@ ความสุขของฉัน!
Theodor Zoulias

1
 [Fact]
        public async Task SimpleTest()
        {
            bool isOK = false;
            Task myTask = new Task(() =>
            {
                Console.WriteLine("Task.BeforeDelay");
                Task.Delay(3000).Wait();
                Console.WriteLine("Task.AfterDelay");
                isOK = true;
                Console.WriteLine("Task.Ended");
            });
            Console.WriteLine("Main.BeforeStart");
            myTask.Start();
            Console.WriteLine("Main.AfterStart");
            await myTask;
            Console.WriteLine("Main.AfterAwait");
            Assert.True(isOK, "OK");
        }

ป้อนคำอธิบายรูปภาพที่นี่


3
คุณตระหนักดีว่าหากปราศจากawaitความล่าช้าของงานจะไม่ทำให้งานนั้นล่าช้าจริง ๆ ใช่ไหม คุณลบฟังก์ชันการทำงาน
nvoigt

ฉันเพิ่งทดสอบ @nvoigt ถูกต้อง: เวลาที่ผ่านไป: 0: 00: 00,0106554 และเราเห็นในภาพหน้าจอของคุณ: "เวลาที่ผ่านไป: 18 ms" ควรจะ> = 1,000 ms
Elo

ใช่คุณพูดถูกฉันอัพเดตคำตอบของฉัน tnx หลังจากที่คุณแสดงความคิดเห็นฉันแก้ปัญหานี้ด้วยการเปลี่ยนแปลงเล็กน้อย :)
BASKA

1
เป็นความคิดที่ดีที่จะใช้การรอ () และไม่รอจากภายในงาน! ขอบคุณมาก!
Elo
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.