cảnh báo cuộc gọi này không được chờ đợi, việc thực hiện phương thức hiện tại vẫn tiếp tục


135

Chỉ cần có VS2012 và cố gắng xử lý async.

Giả sử tôi đã có một phương pháp lấy một số giá trị từ một nguồn chặn. Tôi không muốn người gọi phương thức chặn. Tôi có thể viết phương thức để thực hiện một cuộc gọi lại được gọi khi giá trị đến, nhưng vì tôi đang sử dụng C # 5, tôi quyết định làm cho phương thức không đồng bộ để người gọi không phải xử lý các cuộc gọi lại:

// contrived example (edited in response to Servy's comment)
public static Task<string> PromptForStringAsync(string prompt)
{
    return Task.Factory.StartNew(() => {
        Console.Write(prompt);
        return Console.ReadLine();
    });
}

Đây là một phương pháp ví dụ gọi nó. Nếu PromptForStringAsynckhông đồng bộ, phương thức này sẽ yêu cầu lồng lại một cuộc gọi lại trong một cuộc gọi lại. Với async, tôi có thể viết phương thức của mình theo cách rất tự nhiên này:

public static async Task GetNameAsync()
{
    string firstname = await PromptForStringAsync("Enter your first name: ");
    Console.WriteLine("Welcome {0}.", firstname);

    string lastname = await PromptForStringAsync("Enter your last name: ");
    Console.WriteLine("Name saved as '{0} {1}'.", firstname, lastname);
}

Càng xa càng tốt. Vấn đề là khi tôi gọi GetNameAsync:

public static void DoStuff()
{
    GetNameAsync();
    MainWorkOfApplicationIDontWantBlocked();
}

Toàn bộ vấn đề GetNameAsynclà nó không đồng bộ. Tôi không muốn chặn nó, vì tôi muốn quay lại MainWorkOfApplicationIDontWantBlocked ASAP và để GetNameAsync thực hiện công việc của mình trong nền. Tuy nhiên, gọi nó theo cách này cung cấp cho tôi một cảnh báo trình biên dịch trên GetNameAsyncdòng:

Warning 1   Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Tôi hoàn toàn biết rằng "việc thực hiện phương thức hiện tại vẫn tiếp tục trước khi cuộc gọi kết thúc". Đó là điểm của mã không đồng bộ, phải không?

Tôi thích mã của mình để biên dịch mà không có cảnh báo, nhưng không có gì để "sửa" ở đây vì mã đang làm chính xác những gì tôi dự định làm. Tôi có thể thoát khỏi cảnh báo bằng cách lưu trữ giá trị trả về của GetNameAsync:

public static void DoStuff()
{
    var result = GetNameAsync(); // supress warning
    MainWorkOfApplicationIDontWantBlocked();
}

Nhưng bây giờ tôi có mã thừa. Visual Studio dường như hiểu rằng tôi đã buộc phải viết mã không cần thiết này, bởi vì nó ngăn chặn cảnh báo "giá trị không bao giờ được sử dụng" thông thường.

Tôi cũng có thể loại bỏ cảnh báo bằng cách gói GetNameAsync theo phương thức không đồng bộ:

    public static Task GetNameWrapper()
    {
        return GetNameAsync();
    }

Nhưng điều đó thậm chí còn nhiều hơn nữa đang dư thừa. Vì vậy, tôi phải viết mã tôi không cần hoặc chịu đựng một cảnh báo không cần thiết.

Có điều gì đó về việc tôi sử dụng async không đúng ở đây?


2
BTW, khi thực hiện PromptForStringAsyncbạn làm nhiều việc hơn bạn cần; Chỉ cần trả lại kết quả của Task.Factory.StartNew. Đó là một nhiệm vụ mà giá trị của người đó là chuỗi được nhập trong bàn điều khiển. Không cần phải chờ đợi kết quả trả về; làm như vậy không thêm giá trị mới.
Phục vụ

Wwouldn't nó có ý nghĩa hơn đối với GetNameAsynccung cấp tên đầy đủ mà được cung cấp bởi người sử dụng (ví dụ Task<Name>, thay vì chỉ trả lại một Task? DoStuffSau đó có thể lưu trữ công việc đó, và một trong hai awaitsau khi các phương pháp khác, hoặc thậm chí vượt qua nhiệm vụ mà khác phương pháp để nó có thể awaithoặc Waitnó ở đâu đó bên trong triển khai.
Phục vụ

@Servy: Nếu tôi vừa trả về Tác vụ, tôi gặp lỗi "Vì đây là phương thức không đồng bộ, nên biểu thức trả về phải là loại 'chuỗi' thay vì 'Nhiệm vụ <chuỗi>'".
Bùn

1
Xóa asynctừ khóa.
Phục vụ

13
IMO, đây là một lựa chọn kém cho một cảnh báo về phía nhóm C #. Cảnh báo nên dành cho những thứ gần như chắc chắn sai. Có rất nhiều trường hợp bạn muốn "đốt và quên" một phương thức không đồng bộ, và những lúc khác mà bạn thực sự muốn chờ đợi nó.
MgSam

Câu trả lời:


103

Nếu bạn thực sự không cần kết quả, bạn chỉ cần thay đổi GetNameAsyncchữ ký của mình để trả về void:

public static async void GetNameAsync()
{
    ...
}

Xem xét để xem câu trả lời cho một câu hỏi liên quan: Sự khác biệt giữa trả lại khoảng trống và trả lại một Nhiệm vụ là gì?

Cập nhật

Nếu bạn cần kết quả, bạn có thể thay đổi GetNameAsyncđể trả về, giả sử Task<string>:

public static async Task<string> GetNameAsync()
{
    string firstname = await PromptForStringAsync("Enter your first name: ");
    string lastname = await PromptForStringAsync("Enter your last name: ");
    return firstname + lastname;
}

Và sử dụng nó như sau:

public static void DoStuff()
{
    Task<string> task = GetNameAsync();

    // Set up a continuation BEFORE MainWorkOfApplicationIDontWantBlocked
    Task anotherTask = task.ContinueWith(r => {
            Console.WriteLine(r.Result);
        });

    MainWorkOfApplicationIDontWantBlocked();

    // OR wait for the result AFTER
    string result = task.Result;
}

15
Đó sẽ chỉ là trường hợp nếu anh ta không bao giờ quan tâm đến kết quả, trái ngược với việc không cần kết quả lần này .
Phục vụ

3
@Servy, đúng rồi, cảm ơn đã làm rõ. Nhưng OP GetNameAsynckhông trả lại bất kỳ giá trị nào (tất nhiên ngoại trừ kết quả).
Nikolay Khil

2
Đúng, nhưng bằng cách trả về một tác vụ, anh ta có thể biết khi nào nó hoàn thành việc thực hiện tất cả các hoạt động không đồng bộ. Nếu nó trở lại void, anh ta không có cách nào biết khi nào nó hoàn thành. Đó là những gì tôi muốn nói khi tôi nói "kết quả" trong bình luận trước đây của tôi.
Phục vụ

29
Theo nguyên tắc chung, bạn không bao giờ nên có một async voidphương thức ngoại trừ các trình xử lý sự kiện.
Daniel Mann

2
Một điều cần lưu ý ở đây là hành vi sẽ khác khi nói đến các ngoại lệ không quan sát được khi bạn chuyển sang async voidbất kỳ ngoại lệ nào mà bạn không bắt sẽ làm hỏng quy trình của bạn, nhưng trong .net 4.5 nó sẽ tiếp tục chạy.
Caleb Vear 16/07/2015

62

Tôi khá muộn cho cuộc thảo luận này, nhưng cũng có tùy chọn sử dụng #pragmachỉ thị tiền xử lý. Tôi có một số mã async ở đây và ở đó tôi rõ ràng không muốn chờ đợi trong một số điều kiện và tôi không thích các cảnh báo và các biến không sử dụng giống như phần còn lại của bạn:

#pragma warning disable 4014
SomeMethodAsync();
#pragma warning restore 4014

Xuất "4014"phát từ trang MSDN này: Cảnh báo trình biên dịch (cấp 1) CS4014 .

Xem thêm cảnh báo / câu trả lời của @ ryan-horath tại đây https://stackoverflow.com/a/12145047/928483 .

Các ngoại lệ được đưa ra trong một cuộc gọi không đồng bộ không được chờ đợi sẽ bị mất. Để thoát khỏi cảnh báo này, bạn nên gán giá trị trả về Tác vụ của lệnh gọi async cho một biến. Điều này đảm bảo bạn có quyền truy cập vào bất kỳ ngoại lệ nào được ném, sẽ được chỉ định trong giá trị trả về.

Cập nhật cho C # 7.0

C # 7.0 thêm một tính năng mới, loại bỏ các biến: Loại bỏ - Hướng dẫn C # , cũng có thể giúp về vấn đề này.

_ = SomeMethodAsync();

5
Điều đó hoàn toàn tuyệt vời. Tôi không có ý tưởng bạn có thể làm điều này. Cảm ơn bạn!
Maxim Gershkovich

1
Thậm chí không cần phải nói var, chỉ cần viết_ = SomeMethodAsync();
Ray

41

Tôi không đặc biệt thích các giải pháp gán nhiệm vụ cho một biến không sử dụng hoặc thay đổi chữ ký phương thức để trả về khoảng trống. Cái trước tạo ra mã không cần thiết, không trực quan, trong khi cái sau có thể không thực hiện được nếu bạn đang thực hiện một giao diện hoặc có cách sử dụng chức năng khác mà bạn muốn sử dụng Tác vụ được trả về.

Giải pháp của tôi là tạo ra một phương thức mở rộng của Nhiệm vụ, được gọi là DoNotAwait () mà không làm gì cả. Điều này sẽ không chỉ triệt tiêu tất cả các cảnh báo, ReSharper hay nói cách khác, mà còn làm cho mã dễ hiểu hơn và chỉ ra cho những người duy trì mã trong tương lai của bạn rằng bạn thực sự không muốn chờ cuộc gọi.

Phương pháp mở rộng:

public static class TaskExtensions
{
    public static void DoNotAwait(this Task task) { }
}

Sử dụng:

public static void DoStuff()
{
    GetNameAsync().DoNotAwait();
    MainWorkOfApplicationIDontWantBlocked();
}

Được chỉnh sửa để thêm: điều này tương tự như giải pháp của Jonathan Allen khi phương thức mở rộng sẽ bắt đầu tác vụ nếu chưa bắt đầu, nhưng tôi thích có các chức năng đơn mục đích để ý định của người gọi hoàn toàn rõ ràng.


1
Tôi thích điều này nhưng tôi đã đổi tên nó thành Unawait ();)
Ostati

29

async void LÀ XẤU!

  1. Sự khác biệt giữa trả lại khoảng trống và trả lại một nhiệm vụ là gì?
  2. https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.html

Những gì tôi đề nghị là bạn rõ ràng chạy Task thông qua một phương thức ẩn danh ...

ví dụ

public static void DoStuff()
{
    Task.Run(async () => GetNameAsync());
    MainWorkOfApplicationIDontWantBlocked();
}

Hoặc nếu bạn muốn chặn nó, bạn có thể chờ đợi phương thức ẩn danh

public static void DoStuff()
{
    Task.Run(async () => await GetNameAsync());
    MainWorkOfApplicationThatWillBeBlocked();
}

Tuy nhiên, nếu bạn GetNameAsync phương thức phải tương tác với UI hoặc thậm chí bất kỳ giao diện người dùng nào bị ràng buộc, (WINRT / MVVM, tôi đang nhìn bạn), thì nó sẽ trở nên thú vị hơn một chút =)

Bạn sẽ cần chuyển tham chiếu đến bộ điều phối UI như thế này ...

Task.Run(async () => await GetNameAsync(CoreApplication.MainView.CoreWindow.Dispatcher));

Và sau đó, trong phương thức không đồng bộ của bạn, bạn sẽ cần phải tương tác với các thành phần ràng buộc UI hoặc UI của bạn nghĩ rằng bộ điều phối ...

dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {  this.UserName = userName; });

Phần mở rộng nhiệm vụ của bạn là tuyệt vời. Không biết tại sao tôi chưa thực hiện trước đây.
Mikael Dúi Bolinder

8
Cách tiếp cận đầu tiên bạn đề cập gây ra một cảnh báo khác: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. Điều này cũng khiến một luồng mới được tạo, trong khi đó một luồng mới sẽ không nhất thiết phải được tạo với async / await.
gregsdennis

Bạn nên lên thưa ông!
Ozkan

16

Đây là những gì tôi đang làm:

SomeAyncFunction().RunConcurrently();

Nơi RunConcurrentlyđược định nghĩa là ...

 /// <summary> 
 /// Runs the Task in a concurrent thread without waiting for it to complete. This will start the task if it is not already running. 
 /// </summary> 
 /// <param name="task">The task to run.</param> 
 /// <remarks>This is usually used to avoid warning messages about not waiting for the task to complete.</remarks> 
 public static void RunConcurrently(this Task task) 
 { 
     if (task == null) 
         throw new ArgumentNullException("task", "task is null."); 

     if (task.Status == TaskStatus.Created) 
         task.Start(); 
 } 

https://github.com/docevaad/Anchor/blob/master/Tortuga.Anchor/Tortuga.Anchor.source/spl/TaskUtilities.cs

https://www.nuget.org/packages/Tortuga.Anchor/


1
+1. Điều này có vẻ như nó tránh được tất cả các vấn đề được so sánh trong các bình luận của các câu trả lời khác. Cảm ơn.
Grault

3
Bạn chỉ cần điều này: public static void Forget(this Task task) { }
Shital Shah

1
ý bạn là gì ở đó @ShitalShah
Jay Wick

@ShitalShah sẽ chỉ hoạt động với các tác vụ tự động bắt đầu, chẳng hạn như các tác vụ được tạo bằng async Task. Một số nhiệm vụ cần phải được bắt đầu bằng tay.
Jonathan Allen

7

Theo bài viết của Microsoft về cảnh báo này, bạn có thể giải quyết nó bằng cách chỉ định tác vụ được trả về cho một biến. Dưới đây là bản dịch mã được cung cấp trong ví dụ của Microsoft:

    // To suppress the warning without awaiting, you can assign the 
    // returned task to a variable. The assignment doesn't change how
    // the program runs. However, the recommended practice is always to
    // await a call to an async method.
    // Replace Call #1 with the following line.
    Task delayTask = CalledMethodAsync(delay);

Lưu ý rằng làm điều này sẽ dẫn đến thông báo "Biến cục bộ không bao giờ được sử dụng" trong ReSharper.


Vâng, điều đó ngăn chặn cảnh báo, nhưng không, điều đó không thực sự giải quyết bất cứ điều gì. Taskchức năng quay lại nên được thực hiện awaittrừ khi bạn có lý do rất chính đáng để không. Không có lý do nào ở đây tại sao loại bỏ nhiệm vụ sẽ tốt hơn câu trả lời đã được chấp nhận khi sử dụng một async voidphương thức.

Tôi cũng thích phương pháp void. Điều này làm cho StackOverflow thông minh hơn Microsoft, nhưng tất nhiên đó phải là một thứ nhất định.
chúa tể

4
async voidgiới thiệu các vấn đề nghiêm trọng xung quanh việc xử lý lỗi và dẫn đến mã không thể kiểm tra được (xem bài viết MSDN của tôi ). Sẽ tốt hơn nhiều nếu sử dụng biến - nếu bạn hoàn toàn chắc chắn rằng bạn muốn ngoại lệ âm thầm nuốt. Nhiều khả năng, op sẽ muốn bắt đầu hai Taskgiây và sau đó làm một await Task.WhenAll.
Stephen Cleary

@StephenCleary Cho dù các ngoại lệ từ các tác vụ không quan sát được bỏ qua đều có thể định cấu hình được. Nếu có bất kỳ cơ hội nào, mã của bạn sẽ được sử dụng trong dự án của người khác, đừng để điều đó xảy ra. Một async void DoNotWait(Task t) { await t; }phương thức trợ giúp đơn giản có thể được sử dụng để tránh những nhược điểm của các async voidphương thức mà bạn mô tả. (Và tôi không nghĩ Task.WhenAlllà OP muốn gì, nhưng nó rất có thể.)

@hvd: Phương thức trợ giúp của bạn sẽ làm cho nó có thể kiểm tra được, nhưng nó vẫn có hỗ trợ xử lý ngoại lệ rất kém.
Stephen Cleary

3

Đây, một giải pháp đơn giản.

public static class TasksExtensions
{
    public static void RunAndForget(this Task task)
    {
    }
}

Trân trọng


1

Đó là ví dụ đơn giản hóa của bạn gây ra mã thừa. Thông thường, bạn sẽ muốn sử dụng dữ liệu được tìm nạp từ nguồn chặn tại một số điểm trong chương trình, vì vậy bạn sẽ muốn kết quả quay lại để có thể lấy dữ liệu.

Nếu bạn thực sự có một cái gì đó xảy ra tách biệt hoàn toàn với phần còn lại của chương trình, async sẽ không phải là phương pháp phù hợp. Chỉ cần bắt đầu một chủ đề mới cho nhiệm vụ đó.


Tôi làm muốn sử dụng các dữ liệu lấy từ nguồn ngăn chặn, nhưng tôi không muốn chặn người gọi trong khi tôi chờ đợi nó. Nếu không có async, bạn có thể thực hiện điều này bằng cách chuyển qua một cuộc gọi lại. Trong ví dụ của tôi, tôi có hai phương thức không đồng bộ cần được gọi theo trình tự và cuộc gọi thứ hai cần sử dụng một giá trị được trả về trước. Điều này có nghĩa là xử lý gọi lại lồng nhau, mà xấu như địa ngục. Tài liệu tôi đã đọc cho thấy đây là cụ thể những gì asyncđược thiết kế để dọn dẹp ( ví dụ )
Bùn

@Mud: Chỉ cần gửi kết quả từ cuộc gọi async đầu tiên làm tham số cho cuộc gọi thứ hai. Bằng cách đó, mã trong phương thức thứ hai bắt đầu ngay lập tức và nó có thể đợi kết quả từ cuộc gọi đầu tiên khi cảm thấy như vậy.
Guffa

Tôi có thể làm điều đó, nhưng không có async có nghĩa là các cuộc gọi lại lồng nhau, như thế này: MethodWithCallback((result1) => { Use(result1); MethodWithCallback((result2) => { Use(result1,result2); })Ngay cả trong ví dụ tầm thường này, nó là một con chó cái để phân tích cú pháp. Với async, mã tương đương được tạo cho tôi khi tôi viết result1 = await AsyncMethod(); Use(result1); result2 = await AsyncMethod(); Use(result1,result2); Nó dễ đọc hơn rất nhiều (mặc dù không dễ đọc bị đập vỡ trong bình luận này!)
Mud

@Mud: Vâng. Nhưng bạn có thể gọi phương thức async thứ hai ngay sau lần đầu tiên, không có lý do gì để nó chờ cuộc gọi đồng bộ đến Use.
Guffa

Tôi thực sự không hiểu những gì bạn nói. Phương thứcWithCallback không đồng bộ. Nếu tôi gọi họ quay lại mà không đợi cuộc gọi đầu tiên, cuộc gọi thứ hai có thể kết thúc trước cuộc gọi đầu tiên. Tuy nhiên, tôi cần kết quả của cuộc gọi đầu tiên trong trình xử lý cho lần thứ hai. Vì vậy, tôi phải chờ cuộc gọi đầu tiên.
Bùn

0

Bạn có thực sự muốn bỏ qua kết quả? như trong việc bỏ qua bất kỳ trường hợp ngoại lệ bất ngờ?

Nếu không, bạn có thể muốn xem xét câu hỏi này: Cách tiếp cận Lửa và Quên ,


0

Nếu bạn không muốn thay đổi chữ ký phương thức để trả về void(vì trả về voidluôn luôn là một khoảng trống ed), bạn có thể sử dụng tính năng Hủy bỏ C # 7.0+ như thế này, tốt hơn một chút so với gán cho một biến (và nên loại bỏ hầu hết các biến khác công cụ xác nhận nguồn cảnh báo):

public static void DoStuff()
{
    _ = GetNameAsync(); // we don't need the return value (suppresses warning)
    MainWorkOfApplicationIDontWantBlocked();
}
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.