Cảnh báo loại bỏ CS1998: Phương thức không đồng bộ này thiếu 'chờ đợi'


104

Tôi có một giao diện với một số chức năng không đồng bộ. Một số lớp triển khai giao diện không có bất cứ thứ gì để chờ đợi, và một số có thể chỉ ném. Nó hơi khó chịu với tất cả các cảnh báo.

Khi không sử dụng, hãy chờ đợi trong một chức năng không đồng bộ.

Có thể ngăn chặn tin nhắn?

public async Task<object> test()
{
    throw new NotImplementedException();
}

cảnh báo CS1998: Phương thức không đồng bộ này thiếu toán tử 'chờ đợi' và sẽ chạy đồng bộ. Cân nhắc sử dụng toán tử 'await' để chờ các lệnh gọi API không chặn hoặc 'await Task.Run (...)' để thực hiện công việc liên kết với CPU trên luồng nền.


1
Khi không sử dụng từ khóa await mới trong một hàm được đánh dấu là không đồng bộ.
Simon

Làm thế nào về việc hiển thị cho chúng tôi một mẫu mã tái tạo vấn đề?
John Saunders

Câu trả lời:


106

Tôi có một giao diện với một số chức năng không đồng bộ.

TaskTôi tin rằng các phương pháp quay trở lại . asynclà một chi tiết triển khai, vì vậy nó không thể được áp dụng cho các phương thức giao diện.

Một số lớp triển khai giao diện không có bất cứ thứ gì để chờ đợi, và một số có thể chỉ ném.

Trong những trường hợp này, bạn có thể tận dụng thực tế asynclà chi tiết triển khai.

Nếu bạn không có gì để làm await, thì bạn có thể quay lại Task.FromResult:

public Task<int> Success() // note: no "async"
{
  ... // non-awaiting code
  int result = ...;
  return Task.FromResult(result);
}

Trong trường hợp ném NotImplementedException, thủ tục hơi dài dòng hơn một chút:

public Task<int> Fail() // note: no "async"
{
  var tcs = new TaskCompletionSource<int>();
  tcs.SetException(new NotImplementedException());
  return tcs.Task;
}

Nếu bạn có nhiều phương thức ném NotImplementedException(bản thân nó có thể chỉ ra rằng một số cấu trúc lại ở mức thiết kế sẽ tốt), thì bạn có thể kết hợp wordiness thành một lớp trợ giúp:

public static class TaskConstants<TResult>
{
  static TaskConstants()
  {
    var tcs = new TaskCompletionSource<TResult>();
    tcs.SetException(new NotImplementedException());
    NotImplemented = tcs.Task;
  }

  public static Task<TResult> NotImplemented { get; private set; }
}

public Task<int> Fail() // note: no "async"
{
  return TaskConstants<int>.NotImplemented;
}

Lớp trợ giúp cũng giảm rác mà GC nếu không sẽ phải thu thập, vì mỗi phương thức có cùng kiểu trả về có thể dùng chung các đối tượng Taskvà của nó NotImplementedException.

Tôi có một số ví dụ về loại "hằng số tác vụ" khác trong thư viện AsyncEx của mình .


1
Tôi không nghĩ đến việc mất từ ​​khóa. Như bạn nói, async không liên quan gì đến giao diện. Xấu của tôi, cảm ơn bạn.
Simon

3
Bạn có thể đề xuất một cách tiếp cận mà loại trả về chỉ là Tác vụ (không có kết quả?)
Mike

9
Cảnh báo: Cách tiếp cận này có thể gây ra sự cố vì lỗi sẽ không được truyền theo cách bạn mong đợi. Thông thường, người gọi sẽ mong đợi một ngoại lệ trong phương thức của bạn được hiển thị trong Task. Thay vào đó, phương thức của bạn sẽ ném trước khi nó có cơ hội tạo ra Task. Tôi thực sự nghĩ rằng mô hình tốt nhất là xác định một asyncphương thức không có awaittoán tử. Điều này đảm bảo tất cả mã trong phương thức được coi là một phần của Task.
Bob Meyers

11
Để tránh CS1998, bạn có thể thêm await Task.FromResult(0);vào phương thức của mình. Điều này sẽ không có bất kỳ tác động đáng kể nào về hiệu suất (không giống như Task.Yield ()).
Bob Meyers

3
@AndrewTheken: Ngày nay, bạn chỉ có thể làm return Task.CompletedTask;- điều đơn giản nhất.
Stephen Cleary

63

Một tùy chọn khác, nếu bạn muốn giữ cho phần nội dung của hàm đơn giản và không viết mã để hỗ trợ nó, đơn giản là loại bỏ cảnh báo bằng #pragma:

#pragma warning disable 1998
public async Task<object> Test()
{
    throw new NotImplementedException();
}
#pragma warning restore 1998

Nếu điều này đủ phổ biến, bạn có thể đặt câu lệnh vô hiệu hóa ở đầu tệp và bỏ qua khôi phục.

http://msdn.microsoft.com/en-us/library/441722ys(v=vs.110).aspx


40

Một cách khác để bảo toàn từ khóa async (trong trường hợp bạn muốn giữ nó) là sử dụng:

public async Task StartAsync()
{
    await Task.Yield();
}

Sau khi điền phương thức, bạn có thể chỉ cần xóa câu lệnh. Tôi sử dụng điều này rất nhiều, đặc biệt là khi một phương thức có thể đang chờ đợi điều gì đó nhưng không phải mọi triển khai đều thực sự làm được.


Đây phải là câu trả lời được chấp nhận. Đôi khi việc triển khai giao diện không cần phải là không đồng bộ, điều này gọn gàng hơn nhiều so với gói mọi thứ trong một Task.Runcuộc gọi.
Andrew Theken

12
chờ đợi Task.CompletedTask; // có thể là một lựa chọn tốt hơn
Frode Nilsen

@FrodeNilsen vì một số lý do Task.CompletedTaskdường như không còn tồn tại nữa.
Sebastián Vansteenkiste

1
@ SebastiánVansteenkiste .Net Framework 4.6->, UWP 1.0->, Net Lõi 1.0->
Frode Nilsen

1
@AndrewTheken Tôi đã mất một lúc để đi đến kết luận rằng câu trả lời này và nhận xét của bạn áp dụng cụ thể cho trường hợp triển khai trống hoặc chỉ ném một ngoại lệ (như trong câu hỏi ban đầu). Nếu một triển khai trả về một giá trị, có vẻ như Task.FromResultlà câu trả lời tốt hơn. Cho rằng vấn đề, nếu bạn đang đi để ném một ngoại lệ, có vẻ như câu trả lời khác đã đi vào chơi liên quan đến Task.FromExceptionviệc này không bao giờ là giải pháp lý tưởng. Bạn có đồng ý không?
BlueMonkMN

15

Có sự khác biệt giữa các giải pháp và nói một cách chính xác, bạn nên biết cách người gọi sẽ gọi phương thức không đồng bộ, nhưng với kiểu sử dụng mặc định giả định ".Wait ()" trên kết quả của phương thức - " return Task.CompletedTask " là giải pháp tốt nhất.

    BenchmarkDotNet=v0.10.11, OS=Windows 10 Redstone 3 [1709, Fall Creators Update] (10.0.16299.192)
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233537 Hz, Resolution=309.2589 ns, Timer=TSC
.NET Core SDK=2.1.2
  [Host] : .NET Core 2.0.3 (Framework 4.6.25815.02), 64bit RyuJIT
  Clr    : .NET Framework 4.7 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.2600.0
  Core   : .NET Core 2.0.3 (Framework 4.6.25815.02), 64bit RyuJIT


         Method |  Job | Runtime |         Mean |       Error |      StdDev |       Median |          Min |          Max | Rank |  Gen 0 |  Gen 1 |  Gen 2 | Allocated |
--------------- |----- |-------- |-------------:|------------:|------------:|-------------:|-------------:|-------------:|-----:|-------:|-------:|-------:|----------:|
 CompletedAwait |  Clr |     Clr |    95.253 ns |   0.7491 ns |   0.6641 ns |    95.100 ns |    94.461 ns |    96.557 ns |    7 | 0.0075 |      - |      - |      24 B |
      Completed |  Clr |     Clr |    12.036 ns |   0.0659 ns |   0.0617 ns |    12.026 ns |    11.931 ns |    12.154 ns |    2 | 0.0076 |      - |      - |      24 B |
         Pragma |  Clr |     Clr |    87.868 ns |   0.3923 ns |   0.3670 ns |    87.789 ns |    87.336 ns |    88.683 ns |    6 | 0.0075 |      - |      - |      24 B |
     FromResult |  Clr |     Clr |   107.009 ns |   0.6671 ns |   0.6240 ns |   107.009 ns |   106.204 ns |   108.247 ns |    8 | 0.0584 |      - |      - |     184 B |
          Yield |  Clr |     Clr | 1,766.843 ns |  26.5216 ns |  24.8083 ns | 1,770.383 ns | 1,705.386 ns | 1,800.653 ns |    9 | 0.0877 | 0.0038 | 0.0019 |     320 B |
 CompletedAwait | Core |    Core |    37.201 ns |   0.1961 ns |   0.1739 ns |    37.227 ns |    36.970 ns |    37.559 ns |    4 | 0.0076 |      - |      - |      24 B |
      Completed | Core |    Core |     9.017 ns |   0.0690 ns |   0.0577 ns |     9.010 ns |     8.925 ns |     9.128 ns |    1 | 0.0076 |      - |      - |      24 B |
         Pragma | Core |    Core |    34.118 ns |   0.4576 ns |   0.4281 ns |    34.259 ns |    33.437 ns |    34.792 ns |    3 | 0.0076 |      - |      - |      24 B |
     FromResult | Core |    Core |    46.953 ns |   1.2728 ns |   1.1905 ns |    46.467 ns |    45.674 ns |    49.868 ns |    5 | 0.0533 |      - |      - |     168 B |
          Yield | Core |    Core | 2,480.980 ns | 199.4416 ns | 575.4347 ns | 2,291.978 ns | 1,810.644 ns | 4,085.196 ns |   10 | 0.0916 |      - |      - |     296 B |

Lưu ý: FromResultkhông thể so sánh trực tiếp.

Mã kiểm tra:

   [RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn]
   [ClrJob, CoreJob]
   [HtmlExporter, MarkdownExporter]
   [MemoryDiagnoser]
 public class BenchmarkAsyncNotAwaitInterface
 {
string context = "text context";
[Benchmark]
public int CompletedAwait()
{
    var t = new CompletedAwaitTest();
    var a = t.DoAsync(context);
    a.Wait();
    return t.Length;
}

[Benchmark]
public int Completed()
{
    var t = new CompletedTest();
    var a = t.DoAsync(context);
    a.Wait();
    return t.Length;
}

[Benchmark]
public int Pragma()
{
    var t = new PragmaTest();
    var a = t.DoAsync(context);
    a.Wait();
    return t.Length;
}

[Benchmark]
public int Yield()
{
    var t = new YieldTest();
    var a = t.DoAsync(context);
    a.Wait();
    return t.Length;
}

    [Benchmark]
    public int FromResult()
    {
        var t = new FromResultTest();
        var t2 = t.DoAsync(context);
        return t2.Result;
    }

public interface ITestInterface
{
    int Length { get; }
    Task DoAsync(string context);
}

class CompletedAwaitTest : ITestInterface
{
    public int Length { get; private set; }
    public async Task DoAsync(string context)
    {
        Length = context.Length;
        await Task.CompletedTask;
    }
}

class CompletedTest : ITestInterface
{
    public int Length { get; private set; }
    public Task DoAsync(string context)
    {
        Length = context.Length;
        return Task.CompletedTask;
    }
}

class PragmaTest : ITestInterface
{
    public int Length { get; private set; }
    #pragma warning disable 1998
    public async Task DoAsync(string context)
    {
        Length = context.Length;
        return;
    }
    #pragma warning restore 1998
}

class YieldTest : ITestInterface
{
    public int Length { get; private set; }
    public async Task DoAsync(string context)
    {
        Length = context.Length;
        await Task.Yield();
    }
}

    public interface ITestInterface2
    {
        Task<int> DoAsync(string context);
    }

    class FromResultTest : ITestInterface2
    {
        public async Task<int> DoAsync(string context)
        {
            var i = context.Length;
            return await Task.FromResult(i);
        }
    }

}


1
Thật không may là #pragmamột trong những dường như phải chịu chi phí. Có lẽ chỉ cần nhiều chi phí như thể thay vì trả lại CompletedTaskbạn đã tạo và hoàn thành một AsyncOperation. Thật tuyệt nếu có thể nói với trình biên dịch rằng bạn có thể bỏ qua điều đó khi phương thức này vẫn chạy đồng bộ.
binki

Làm thế nào tương tự để bạn cho Task.CompletedTasklà tương tự Task.FromResult? Sẽ rất thú vị khi biết - Tôi hy vọng FromResult sẽ là tương tự nhất và vẫn là trình hoạt động tốt nhất nếu bạn phải trả về một giá trị.
BlueMonkMN

Tôi sẽ thêm nó. Tôi nghĩ rằng mã máy nhà nước sẽ tiết hơn trong trường hợp này và CompletedTask sẽ giành chiến thắng .Let thấy
La Mã Pokrovskij

1
Sẽ rất vui khi thấy điều này được cập nhật cho .NET Core 2.2, vì phân bổ trong các máy trạng thái không đồng bộ đã được cải thiện đáng kể
Tseng

1
@Tseng Tôi đã chạy các điểm chuẩn trên .NET Core 2.2.0. Rõ ràng, tổng thời gian là khác nhau do phần cứng khác nhau, nhưng tỷ lệ vẫn gần giống nhau: Phương pháp | .NET Core 2.0.3 Nghĩa là | .NET Core 2.2.0 Nghĩa là Hoàn thành | 100% | Đã hoàn thành 100% đang chờ đợi | 412,57% | 377,22% Từ kết quả | 520,72% | 590,89% Pragma | 378,37% | 346,64% lợi nhuận | 27514,47% | 23602,38%
Bão

10

Tôi biết đây là một chuỗi cũ và có lẽ điều này sẽ không có tác dụng phù hợp với tất cả các cách sử dụng, nhưng những điều sau đây gần như tôi có thể đơn giản là ném NotImplementedException khi tôi chưa triển khai một phương thức, mà không làm thay đổi chữ ký phương thức. Nếu nó có vấn đề, tôi rất vui khi biết về nó, nhưng nó hầu như không quan trọng với tôi: Dù sao thì tôi cũng chỉ sử dụng cái này khi đang phát triển, vì vậy nó hoạt động như thế nào không phải là điều quan trọng. Tuy nhiên, tôi rất vui khi biết lý do tại sao đó là một ý tưởng tồi, nếu đúng như vậy.

public async Task<object> test()
{
    throw await new AwaitableNotImplementedException<object>();
}

Đây là loại tôi đã thêm để làm cho điều đó có thể.

public class AwaitableNotImplementedException<TResult> : NotImplementedException
{
    public AwaitableNotImplementedException() { }

    public AwaitableNotImplementedException(string message) : base(message) { }

    // This method makes the constructor awaitable.
    public TaskAwaiter<AwaitableNotImplementedException<TResult>> GetAwaiter()
    {
        throw this;
    }
}

10

Chỉ là một bản cập nhật cho Stephen's Answer, bạn không cần phải viết TaskConstantslớp nữa vì có một phương thức trợ giúp mới:

    public Task ThrowException()
    {
        try
        {
            throw new NotImplementedException();
        }
        catch (Exception e)
        {
            return Task.FromException(e);
        }
    }

3
Đừng làm điều này. Dấu vết ngăn xếp sẽ không chỉ vào mã của bạn. Các ngoại lệ phải được ném để được khởi tạo hoàn toàn.
Daniel B

1
Daniel B - Vâng, bạn hoàn toàn đúng. Tôi đã sửa đổi câu trả lời của mình để ném chính xác ngoại lệ.
Matt

3

Trong trường hợp bạn đã liên kết với Tiện ích mở rộng phản ứng, bạn cũng có thể thực hiện:

public async Task<object> NotImplemented()
{
    await Observable.Throw(new NotImplementedException(), null as object).ToTask();
}

public async Task<object> SimpleResult()
{
    await Observable.Return(myvalue).ToTask();
}

Reactive và async / await đều rất tuyệt vời, nhưng chúng cũng chơi tốt với nhau.

Bao gồm cần thiết là:

using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;

3

Nó có thể xảy ra cs1998 bên dưới.

public async Task<object> Foo()
{
    return object;
}

Sau đó, bạn có thể cải cách bên dưới.

public async Task<object> Foo()
{
    var result = await Task.Run(() =>
    {
        return object;
    });
    return result;
}

3

Bạn có thể thử điều này:

public async Task<object> test()
{
await Task.CompletedTask; 
}


1

Nếu bạn không có gì để chờ đợi, hãy trả lại Task.FromResult

public Task<int> Success() // note: no "async"
{
  ... // Do not have await code
  var result = ...;
  return Task.FromResult(result);
}

1

Đây là một số lựa chọn thay thế tùy thuộc vào chữ ký phương pháp của bạn.

    public async Task Test1()
    {
        await Task.CompletedTask;
    }

    public async Task<object> Test2()
    {
        return await Task.FromResult<object>(null);
    }

    public async Task<object> Test3()
    {
        return await Task.FromException<object>(new NotImplementedException());
    }

-1
// This is to get rid of warning CS1998, please remove when implementing this method.
await new Task(() => { }).ConfigureAwait(false);
throw new NotImplementedException();

-2

Bạn có thể loại bỏ từ khóa async khỏi phương thức và chỉ cần nó trả về Task;

    public async Task DoTask()
    {
        State = TaskStates.InProgress;
        await RunTimer();
    }

    public Task RunTimer()
    {
        return new Task(new Action(() =>
        {
            using (var t = new time.Timer(RequiredTime.Milliseconds))
            {
                t.Elapsed += ((x, y) => State = TaskStates.Completed);
                t.Start();
            }
        }));
    }
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.