Bỏ qua cảnh báo của CS4014: Vì cuộc gọi này không được chờ đợi, nên việc thực hiện phương thức hiện tại vẫn tiếp tục


156

Đây không phải là bản sao của "Cách gọi phương thức async một cách an toàn trong C # mà không phải chờ đợi" .

Làm thế nào để tôi tuyệt đối ngăn chặn cảnh báo sau đây?

cảnh báo CS4014: Vì cuộc gọi này không được chờ đợi, nên việc thực hiện phương thức hiện tại tiếp tục trước khi cuộc gọi được hoàn thành. Cân nhắc áp dụng toán tử 'await' cho kết quả của cuộc gọi.

Một ví dụ đơn giản:

static async Task WorkAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("Done!");
}

static async Task StartWorkAsync()
{
    WorkAsync(); // I want fire-and-forget 

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}

Những gì tôi đã thử và không thích:

static async Task StartWorkAsync()
{
    #pragma warning disable 4014
    WorkAsync(); // I want fire-and-forget here
    #pragma warning restore 4014
    // ...
}

static async Task StartWorkAsync()
{
    var ignoreMe = WorkAsync(); // I want fire-and-forget here
    // ...
}

Đã cập nhật , vì câu trả lời được chấp nhận ban đầu đã được chỉnh sửa, tôi đã thay đổi câu trả lời được chấp nhận thành câu trả lời bằng cách sử dụng loại bỏ C # 7.0 , vì tôi không nghĩ ContinueWithlà phù hợp ở đây. Bất cứ khi nào tôi cần ghi lại các trường hợp ngoại lệ cho các hoạt động quên và quên, tôi sử dụng một phương pháp phức tạp hơn được đề xuất bởi Stephen Cleary tại đây .


1
Vì vậy, bạn nghĩ #pragmalà không tốt đẹp?
Frédéric Hamidi

10
@ Frédéric Hamidi, tôi làm.
noseratio

2
@Noseratio: À, đúng rồi. Xin lỗi, tôi nghĩ đó là cảnh báo khác. Phớt lờ tôi!
Jon Skeet

3
@Terribad: Tôi không thực sự chắc chắn - có vẻ như cảnh báo là khá hợp lý cho hầu hết các trường hợp. Cụ thể, bạn nên suy nghĩ về những gì bạn muốn xảy ra với bất kỳ thất bại nào - thường là ngay cả khi "cháy và quên", bạn nên tìm ra cách ghi nhật ký thất bại, v.v.
Jon Skeet

4
@Terribad, trước khi bạn sử dụng nó theo cách này hay cách khác, bạn nên có một bức tranh rõ ràng về cách thức ngoại lệ được truyền bá cho các phương thức async (kiểm tra điều này ). Sau đó, câu trả lời của @ Knaģis cung cấp một cách thanh lịch để không mất bất kỳ ngoại lệ nào đối với việc quên và quên, thông qua một async voidphương thức trợ giúp.
chơi

Câu trả lời:


160

Với C # 7, giờ đây bạn có thể sử dụng loại bỏ :

_ = WorkAsync();

7
Đây là một tính năng ngôn ngữ nhỏ tiện dụng mà tôi không thể nhớ được. Nó giống như có một _ = ...trong não của tôi.
Marc L.

3
Tôi đã tìm thấy SupressMessage đã xóa cảnh báo của tôi khỏi "Danh sách lỗi" của Visual Studio nhưng không phải là "Đầu ra" và #pragma warning disable CSxxxxtrông xấu hơn so với loại bỏ;)
David Savage

122

Bạn có thể tạo một phương thức mở rộng sẽ ngăn cảnh báo. Phương thức mở rộng có thể trống hoặc bạn có thể thêm xử lý ngoại lệ với .ContinueWith()đó.

static class TaskExtensions
{
    public static void Forget(this Task task)
    {
        task.ContinueWith(
            t => { WriteLog(t.Exception); },
            TaskContinuationOptions.OnlyOnFaulted);
    }
}

public async Task StartWorkAsync()
{
    this.WorkAsync().Forget();
}

Tuy nhiên, ASP.NET tính số lượng tác vụ đang chạy, do đó, nó sẽ không hoạt động với Forget()phần mở rộng đơn giản như được liệt kê ở trên và thay vào đó có thể thất bại với ngoại lệ:

Một mô-đun hoặc trình xử lý không đồng bộ hoàn thành trong khi một hoạt động không đồng bộ vẫn đang chờ xử lý.

Với .NET 4.5.2, nó có thể được giải quyết bằng cách sử dụng HostingEnvironment.QueueBackgroundWorkItem:

public static Task HandleFault(this Task task, CancellationToken cancelToken)
{
    return task.ContinueWith(
        t => { WriteLog(t.Exception); },
        cancelToken,
        TaskContinuationOptions.OnlyOnFaulted,
        TaskScheduler.Default);
}

public async Task StartWorkAsync()
{
    System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem(
        cancelToken => this.WorkAsync().HandleFault(cancelToken));
}

8
Tôi đã tìm thấy TplExtensions.Forget. Có nhiều điều tốt đẹp hơn theo Microsoft.VisualStudio.Threading. Tôi ước nó được cung cấp để sử dụng ngoài Visual Studio SDK.
noseratio

1
@Noseratio và Knagis, tôi thích cách tiếp cận này và tôi dự định sử dụng nó. Tôi đã đăng một câu hỏi tiếp theo có liên quan: stackoverflow.com/questions/22864367/fire-and-forget-approach
Matt Smith

3
@stricq Mục đích nào sẽ thêm ConfigureAwait (false) vào Forget () phục vụ? Theo tôi hiểu, ConfigureAwait chỉ ảnh hưởng đến đồng bộ hóa luồng tại thời điểm chờ đợi được sử dụng trên Tác vụ, nhưng mục đích của Quên () là vứt bỏ Tác vụ, do đó, Nhiệm vụ không bao giờ được chờ đợi, vì vậy ConfigureAwait ở đây là vô nghĩa.
dthorpe

3
Nếu luồng sinh sản biến mất trước khi nhiệm vụ cháy và quên hoàn thành, không có ConfigureAwait (false), nó vẫn sẽ cố gắng tự sắp xếp lại vào luồng sinh sản, luồng đó sẽ biến mất. Cài đặt ConfigureAwait (false) yêu cầu hệ thống không quay lại chuỗi xử lý cuộc gọi.
stricq

2
Câu trả lời này có một chỉnh sửa để quản lý một trường hợp cụ thể, cộng với hàng tá ý kiến. Đơn giản mọi thứ thường là những điều đúng đắn, hãy bỏ đi! Và tôi trích dẫn câu trả lời @ fjch1997: Thật ngu ngốc khi tạo ra một phương thức cần thêm một vài tích tắc để thực hiện, chỉ với mục đích triệt tiêu cảnh báo.
Teejay

39

Bạn có thể trang trí phương thức với thuộc tính sau:

[System.Diagnostics.CodeAnalysis.SuppressMessage("Await.Warning", "CS4014:Await.Warning")]
static async Task StartWorkAsync()
{
    WorkAsync();
    // ...
}

Về cơ bản, bạn đang nói với trình biên dịch rằng bạn biết những gì bạn đang làm và nó không cần phải lo lắng về sai lầm có thể xảy ra.

Phần quan trọng của mã này là tham số thứ hai. Phần "CS4014:" là phần ngăn chặn cảnh báo. Bạn có thể viết bất cứ điều gì bạn muốn trên phần còn lại.


Không hoạt động với tôi: Visual Studio cho Mac 7.0.1 (bản dựng 24). Có vẻ như nó nên nhưng - không.
IronRod

1
[SuppressMessage("Compiler", "CS4014")]chặn thông báo trong cửa sổ Danh sách lỗi, nhưng cửa sổ đầu ra vẫn hiển thị một dòng cảnh báo
David Ching

35

Hai cách của tôi để đối phó với điều này.

Lưu nó vào một biến loại bỏ (C # 7)

Thí dụ

_ = Task.Run(() => DoMyStuff()).ConfigureAwait(false);

Kể từ khi giới thiệu loại bỏ trong C # 7, bây giờ tôi cho rằng điều này tốt hơn là áp dụng cảnh báo. Bởi vì nó không chỉ thay thế cảnh báo, mà còn làm cho ý định cháy và quên rõ ràng.

Hơn nữa, trình biên dịch sẽ có thể tối ưu hóa nó đi trong chế độ phát hành.

Chỉ cần đàn áp nó

#pragma warning disable 4014
...
#pragma warning restore 4014

là một giải pháp đủ tốt để "đốt và quên".

Lý do tại sao cảnh báo này tồn tại là vì trong nhiều trường hợp, bạn không có ý định sử dụng một phương thức trả về nhiệm vụ mà không chờ đợi nó. Ức chế cảnh báo khi bạn có ý định bắn và quên có ý nghĩa.

Nếu bạn gặp khó khăn khi nhớ cách đánh vần #pragma warning disable 4014, chỉ cần để Visual Studio thêm nó cho bạn. Nhấn Ctrl +. để mở "Thao tác nhanh" và sau đó "Bỏ qua CS2014"

Tất cả trong tất cả

Thật ngu ngốc khi tạo ra một phương thức cần thêm một vài tích tắc để thực hiện, chỉ với mục đích triệt tiêu cảnh báo.


Điều này đã làm việc trong Visual Studio cho Mac 7.0.1 (bản dựng 24).
IronRod

1
Thật ngu ngốc khi tạo ra một phương thức cần thêm một vài tích tắc để thực hiện, chỉ với mục đích triệt tiêu cảnh báo - cách này không thêm dấu tích nào cả và IMO dễ đọc hơn:[MethodImpl(MethodImplOptions.AggressiveInlining)] void Forget(this Task @this) { } /* ... */ obj.WorkAsync().Forget();
noseratio

1
@Noseratio Rất nhiều lần khi tôi sử dụng AggressiveInlining trình biên dịch chỉ cần bỏ qua nó vì bất kỳ lý do gì
fjch1997

1
Tôi thích tùy chọn pragma, vì nó siêu đơn giản và chỉ áp dụng cho dòng (hoặc phần) mã hiện tại, không phải toàn bộ phương thức.
wasatchwizard

2
Đừng quên sử dụng mã lỗi như #pragma warning disable 4014sau đó để khôi phục cảnh báo sau đó #pragma warning restore 4014. Nó vẫn hoạt động mà không có mã lỗi, nhưng nếu bạn không thêm số lỗi thì nó sẽ chặn tất cả các tin nhắn.
DunningKrugerEffect

11

Một cách dễ dàng để dừng cảnh báo là chỉ cần gán Nhiệm vụ khi gọi nó:

Task fireAndForget = WorkAsync(); // No warning now

Và trong bài viết gốc của bạn, bạn sẽ làm:

static async Task StartWorkAsync()
{
    // Fire and forget
    var fireAndForget = WorkAsync(); // Tell the compiler you know it's a task that's being returned 

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}

Tôi đã đề cập đến phương pháp này trong chính câu hỏi, như một trong những điều tôi không đặc biệt thích.
noseratio

Rất tiếc! Không nhận thấy rằng vì nó nằm trong cùng một đoạn mã với phần thực dụng của bạn ... Và tôi đang tìm kiếm câu trả lời. Bên cạnh đó, bạn không thích phương pháp này là gì?
noelicus

1
Tôi không giống tasknhư một biến cục bộ bị lãng quên. Gần như trình biên dịch sẽ cho tôi một cảnh báo khác, một cái gì đó như " taskđược gán nhưng giá trị của nó không bao giờ được sử dụng", ngoài ra nó không. Ngoài ra, nó làm cho mã ít đọc hơn. Bản thân tôi sử dụng phương pháp này .
noseratio

Đủ công bằng - tôi có một cảm giác tương tự đó là lý do tại sao tôi đặt tên cho nó fireAndForget... vì vậy tôi hy vọng nó sẽ không được kiểm chứng.
noelicus

4

Lý do cảnh báo là WorkAsync đang trả lại một Taskthứ không bao giờ được đọc hoặc chờ đợi. Bạn có thể đặt loại trả về WorkAsync thành voidvà cảnh báo sẽ biến mất.

Thông thường một phương thức trả về một Taskkhi người gọi cần biết trạng thái của công nhân. Trong trường hợp quên và quên, khoảng trống sẽ được trả về để giống với việc người gọi độc lập với phương thức được gọi.

static async void WorkAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("Done!");
}

static async Task StartWorkAsync()
{
    WorkAsync(); // no warning since return type is void

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}

2

Tại sao không bọc nó bên trong một phương thức async trả về void? Một chút dài nhưng tất cả các biến được sử dụng.

static async Task StartWorkAsync()
{   
     async void WorkAndForgetAsync() => await WorkAsync();
     WorkAndForgetAsync(); // no warning
}

1

Tôi tìm thấy phương pháp này một cách tình cờ ngày hôm nay. Bạn có thể xác định một ủy nhiệm và gán phương thức async cho đại biểu trước.

    delegate Task IntermediateHandler();



    static async Task AsyncOperation()
    {
        await Task.Yield();
    }

và gọi nó như vậy

(new IntermediateHandler(AsyncOperation))();

...

Tôi nghĩ thật thú vị khi trình biên dịch sẽ không đưa ra cảnh báo chính xác tương tự khi sử dụng đại biểu.


Không cần phải khai báo một đại biểu, bạn cũng có thể làm được (new Func<Task>(AsyncOperation))()mặc dù IMO vẫn còn quá dài dòng.
noseratio
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.