Làm thế nào để tuyên bố một nhiệm vụ chưa bắt đầu sẽ chờ đợi cho một nhiệm vụ khác?


9

Tôi đã thực hiện Bài kiểm tra đơn vị này và tôi không hiểu tại sao "chờ đợi Nhiệm vụ.Delay ()" không chờ đợi!

   [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");
    }

Đây là đầu ra Kiểm tra đơn vị:

Đầu ra thử nghiệm đơn vị

Làm thế nào điều này có thể "chờ đợi" không chờ đợi, và chủ đề chính tiếp tục?


Nó hơi không rõ ràng những gì bạn đang cố gắng để đạt được. Bạn có thể vui lòng thêm đầu ra dự kiến ​​của bạn?
OlegI

1
Phương pháp thử nghiệm rất rõ ràng - dự kiến ​​isOK là đúng
Ngài Rufo

Không có lý do để tạo ra một nhiệm vụ không bắt đầu. Nhiệm vụ không phải là chủ đề, họ sử dụng chủ đề. Bạn đang cố làm gì vậy? Tại sao không sử dụng Task.Run()sau lần đầu tiên Console.WriteLine?
Panagiotis Kanavos

1
@Elo bạn vừa mô tả threadpools. Bạn không cần một nhóm nhiệm vụ để thực hiện hàng đợi công việc. Bạn cần một hàng công việc, ví dụ: các đối tượng Hành động <T>
Panagiotis Kanavos

2
@Elo những gì bạn cố gắng làm đã có sẵn trong .NET, ví dụ như thông qua các lớp TPL Dataflow như ActionBlock hoặc các lớp System.Threading.Channels mới hơn. Bạn có thể tạo ActionBlock để nhận và xử lý tin nhắn bằng một hoặc nhiều tác vụ đồng thời. Tất cả các khối có bộ đệm đầu vào với khả năng cấu hình. DOP và dung lượng cho phép bạn kiểm soát đồng thời, yêu cầu điều tiết và thực hiện áp lực - nếu có quá nhiều tin nhắn được xếp hàng, nhà sản xuất chờ đợi
Panagiotis Kanavos

Câu trả lời:


8

new Task(async () =>

Một nhiệm vụ không mất một Func<Task>, nhưng một Action. Nó sẽ gọi phương thức không đồng bộ của bạn và hy vọng nó kết thúc khi nó trả về. Nhưng nó không. Nó trả về một nhiệm vụ. Nhiệm vụ đó không được chờ đợi bởi nhiệm vụ mới. Đối với tác vụ mới, công việc được thực hiện khi phương thức được trả về.

Bạn cần sử dụng tác vụ đã tồn tại thay vì gói nó trong một tác vụ mới:

[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() => ... )cũng là một lựa chọn
Ngài Rufo

Bạn vừa làm giống như một tác giả của câu hỏi
OlegI

BTW myTask.Start();sẽ tăng mộtInvalidOperationException
Ngài Rufo

@OlegI Tôi không thấy nó. Bạn có thể giải thích nó được không?
nvoigt

@nvoigt Tôi cho rằng anh ta có nghĩa là myTask.Start()sẽ mang lại một ngoại lệ cho sự thay thế của anh ta và nên được gỡ bỏ khi sử dụng Task.Run(...). Không có lỗi trong giải pháp của bạn.
404

3

Vấn đề là bạn đang sử dụng lớp không chung chung Task, điều đó không có nghĩa là tạo ra kết quả. Vì vậy, khi bạn tạo Taskcá thể thông qua một ủy nhiệm async:

Task myTask = new Task(async () =>

... Đại biểu được coi là async void. Một async voidkhông phải là một Task, nó không thể được chờ đợi, ngoại lệ của nó không thể được xử lý, và đó là một nguồn gốc của hàng ngàn câu hỏi được thực hiện bởi các lập trình viên thất vọng ở đây trong StackOverflow và các nơi khác. Giải pháp là sử dụng Task<TResult>lớp chung , vì bạn muốn trả về một kết quả và kết quả là một kết quả khác Task. Vì vậy, bạn phải tạo một Task<Task>:

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

Bây giờ khi bạn Startở bên ngoài, Task<Task>nó sẽ được hoàn thành gần như ngay lập tức vì công việc của nó chỉ là tạo ra bên trong Task. Sau đó, bạn sẽ phải chờ đợi bên trong Tasklà tốt. Đây là cách nó có thể được thực hiện:

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

Bạn có hai lựa chọn thay thế. Nếu bạn không cần một tài liệu tham khảo rõ ràng cho bên trong Taskthì bạn chỉ có thể chờ đợi bên ngoài Task<Task>hai lần:

await await myTask;

... hoặc bạn có thể sử dụng phương thức tiện ích mở rộng tích Unwraphợp kết hợp các tác vụ bên ngoài và bên trong thành một:

await myTask.Unwrap();

Việc hủy ghép này tự động xảy ra khi bạn sử dụng Task.Runphương thức phổ biến hơn nhiều để tạo ra các tác vụ nóng, vì vậy Unwrapngày nay không được sử dụng thường xuyên.

Trong trường hợp bạn quyết định rằng đại biểu không đồng bộ của bạn phải trả về một kết quả, ví dụ a string, thì bạn nên khai báo myTaskbiến là loại Task<Task<string>>.

Lưu ý: Tôi không tán thành việc sử dụng các hàm Tasktạo để tạo các tác vụ lạnh. Vì thông thường người ta thường cau mày, vì những lý do tôi không thực sự biết, nhưng có lẽ vì nó hiếm khi được sử dụng nên nó có khả năng khiến người dùng / người bảo trì / người đánh giá mã không biết đến bất ngờ.

Lời khuyên chung: Hãy cẩn thận mỗi khi bạn cung cấp một đại biểu không đồng bộ làm đối số cho một phương thức. Phương pháp này lý tưởng nhất là mong đợi một Func<Task>đối số (có nghĩa là hiểu các đại biểu không đồng bộ) hoặc ít nhất là một Func<T>đối số (có nghĩa là ít nhất là đối số được tạo Tasksẽ không bị bỏ qua). Trong trường hợp không may là phương pháp này chấp nhận Action, đại biểu của bạn sẽ được coi là async void. Điều này hiếm khi là những gì bạn muốn, nếu có bao giờ.


Làm thế nào một câu trả lời kỹ thuật mất giá! cảm ơn bạn.
Elo

@Elo niềm vui của tôi!
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");
        }

nhập mô tả hình ảnh ở đây


3
Bạn nhận ra rằng nếu không có await, sự chậm trễ nhiệm vụ sẽ không thực sự trì hoãn nhiệm vụ đó, phải không? Bạn đã loại bỏ chức năng.
nvoigt

Tôi vừa thử nghiệm, @nvoigt đã đúng: Thời gian đã trôi qua: 0: 00: 00.0106554. Và chúng tôi thấy trong ảnh chụp màn hình của bạn: "thời gian đã trôi qua: 18 ms", nên là> = 1000 ms
Elo

Có bạn đúng, tôi cập nhật phản hồi của tôi. Tnx. Sau khi bạn bình luận tôi giải quyết điều này với những thay đổi tối thiểu. :)
BASKA

1
Ý tưởng tốt để sử dụng Wait () và không chờ đợi từ bên trong Nhiệm vụ! cảm ơn !
Elo
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.