ระงับคำเตือน CS1998: วิธีการ async นี้ไม่มี 'รอ'


104

ฉันมีอินเทอร์เฟซที่มีฟังก์ชัน async บางอย่าง บางคลาสที่ใช้อินเทอร์เฟซไม่ได้มีอะไรให้รอและบางคลาสอาจจะโยนทิ้ง มันน่ารำคาญเล็กน้อยกับคำเตือนทั้งหมด

เมื่อไม่ใช้ await ในฟังก์ชัน async

สามารถระงับข้อความได้หรือไม่?

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

คำเตือน CS1998: วิธีการ async นี้ไม่มีตัวดำเนินการ "รอ" และจะทำงานพร้อมกัน พิจารณาใช้ตัวดำเนินการ 'await' เพื่อรอการเรียก API ที่ไม่ปิดกั้นหรือ 'await Task.Run (... )' เพื่อทำงานที่ผูกกับ CPU บนเธรดพื้นหลัง


1
เมื่อไม่ใช้คีย์เวิร์ด await ใหม่ในฟังก์ชันที่มีเครื่องหมาย async
Simon

จะแสดงตัวอย่างโค้ดที่ทำให้เกิดปัญหาได้อย่างไร
John Saunders

คำตอบ:


107

ฉันมีอินเทอร์เฟซที่มีฟังก์ชัน async บางอย่าง

Taskฉันเชื่อว่าวิธีการกลับมา asyncเป็นรายละเอียดการใช้งานดังนั้นจึงไม่สามารถใช้กับวิธีการอินเทอร์เฟซได้

บางคลาสที่ใช้อินเทอร์เฟซไม่ได้มีอะไรให้รอและบางคลาสอาจจะโยนทิ้ง

ในกรณีเหล่านี้คุณสามารถใช้ประโยชน์จากข้อเท็จจริงที่asyncเป็นรายละเอียดการนำไปใช้งานได้

หากคุณไม่มีอะไรให้awaitคุณสามารถกลับมาTask.FromResult:

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

ในกรณีของการขว้างปาNotImplementedExceptionขั้นตอนจะซับซ้อนกว่าเล็กน้อย:

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

หากคุณมีวิธีการขว้างปามากมายNotImplementedException(ซึ่งอาจบ่งบอกได้ว่าการปรับโครงสร้างระดับการออกแบบบางอย่างจะดี) คุณสามารถสรุปความเป็นตัวเองลงในคลาสตัวช่วย:

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;
}

คลาสผู้ช่วยยังลดขยะที่ GC จะต้องรวบรวมเนื่องจากแต่ละวิธีที่มีประเภทการส่งคืนเดียวกันสามารถแชร์TaskและNotImplementedExceptionวัตถุได้

ฉันมีอีกหลายงาน "คงที่" ตัวอย่างประเภทในห้องสมุด


1
ฉันไม่คิดว่าจะสูญเสียคำหลัก อย่างที่คุณพูด async ไม่มีส่วนเกี่ยวข้องกับอินเทอร์เฟซ ฉันไม่ดีขอบคุณ
Simon

3
คุณช่วยแนะนำวิธีการที่ประเภทผลตอบแทนเป็นเพียงงาน (โดยไม่มีผลลัพธ์)
ไมค์

9
คำเตือน:วิธีนี้อาจทำให้เกิดปัญหาได้เนื่องจากข้อผิดพลาดจะไม่ถูกเผยแพร่อย่างที่คุณคาดหวัง โดยปกติผู้โทรจะคาดหวังว่าข้อยกเว้นในวิธีการของคุณจะปรากฏขึ้นภายในไฟล์Task. แต่วิธีการของคุณจะโยนก่อนที่จะมีโอกาสสร้างไฟล์Task. ฉันคิดว่ารูปแบบที่ดีที่สุดคือการกำหนดasyncวิธีการโดยไม่มีawaitตัวดำเนินการ สิ่งนี้ทำให้มั่นใจได้ว่าโค้ดภายในวิธีการทั้งหมดได้รับการปฏิบัติเป็นส่วนหนึ่งของไฟล์Task.
Bob Meyers

11
เพื่อหลีกเลี่ยง CS1998 คุณสามารถเพิ่มawait Task.FromResult(0);วิธีการของคุณได้ สิ่งนี้ไม่ควรมีผลกระทบอย่างมีนัยสำคัญ (ไม่เหมือน Task.Yield ())
Bob Meyers

3
@AndrewTheken: วันนี้คุณทำได้return Task.CompletedTask;- ง่ายที่สุด
Stephen Cleary

63

อีกทางเลือกหนึ่งหากคุณต้องการให้เนื้อหาของฟังก์ชันเรียบง่ายและไม่ต้องเขียนโค้ดเพื่อรองรับก็เพียงแค่ระงับคำเตือนด้วย #pragma:

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

หากเป็นเรื่องปกติมากพอคุณสามารถวางคำสั่งปิดการใช้งานไว้ที่ด้านบนของไฟล์และละเว้นการคืนค่า

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


42

อีกวิธีหนึ่งในการรักษาคีย์เวิร์ด async (ในกรณีที่คุณต้องการเก็บไว้) คือการใช้:

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

เมื่อคุณเติมเมธอดคุณสามารถลบคำสั่ง ฉันใช้สิ่งนี้บ่อยมากโดยเฉพาะอย่างยิ่งเมื่อวิธีการอาจรอบางสิ่งบางอย่าง แต่ไม่ใช่ทุกการนำไปใช้จริง


นี่ควรเป็นคำตอบที่ได้รับการยอมรับ บางครั้งการใช้งานอินเทอร์เฟซไม่จำเป็นต้องเป็นแบบ async ซึ่งจะสะอาดกว่าการรวมทุกอย่างในการTask.Runโทร
Andrew Theken

12
รอ Task.CompletedTask; // อาจเป็นตัวเลือกที่ดีกว่า
Frode Nilsen

@FrodeNilsen ด้วยเหตุผลบางอย่างTask.CompletedTaskดูเหมือนจะไม่มีอีกต่อไป
Sebastián Vansteenkiste

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

1
@AndrewTheken ฉันใช้เวลาสักพักกว่าจะได้ข้อสรุปว่าคำตอบนี้และความคิดเห็นของคุณใช้เฉพาะกับกรณีที่การนำไปใช้งานว่างเปล่าหรือเพียงแค่แสดงข้อยกเว้น (เช่นเดียวกับคำถามเดิม) หากการใช้งานส่งคืนค่าดูเหมือนว่าTask.FromResultจะเป็นคำตอบที่ดีกว่า สำหรับเรื่องที่ถ้าคุณกำลังจะไปโยนยกเว้นดูเหมือนว่าคำตอบอื่นได้เข้ามาเล่นเกี่ยวกับTask.FromExceptionการทำเรื่องนี้ไม่เคยเป็นทางออกที่ดี คุณจะเห็นด้วย?
BlueMonkMN

15

มีความแตกต่างระหว่างวิธีแก้ปัญหาและการพูดอย่างเคร่งครัดคุณควรรู้ว่าผู้โทรจะเรียกเมธอด async อย่างไร แต่ด้วยรูปแบบการใช้งานเริ่มต้นที่ถือว่า ".Wait ()" ในผลลัพธ์ของวิธีการ - " return Task.CompletedTask " เป็นทางออกที่ดีที่สุด

    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 |

หมายเหตุ: FromResultไม่สามารถเปรียบเทียบโดยตรง

รหัสทดสอบ:

   [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
เป็นเรื่องโชคร้ายที่#pragmaดูเหมือนว่าจะต้องเสียค่าใช้จ่าย อาจเป็นเพียงค่าใช้จ่ายที่มากพอ ๆ กับแทนที่จะส่งคืนCompletedTaskคุณสร้างและทำAsyncOperationไฟล์. คงจะดีไม่น้อยหากสามารถบอกคอมไพเลอร์ได้ว่าสามารถข้ามไปได้เมื่อเมธอดทำงานพร้อมกัน
binki

คุณคิดว่าTask.CompletedTaskคล้ายกันTask.FromResultแค่ไหน? มันน่าสนใจที่จะรู้ - ฉันคาดว่า FromResult จะคล้ายคลึงกันมากที่สุดและยังคงเป็นนักแสดงที่ดีที่สุดหากคุณต้องคืนค่า
BlueMonkMN

ฉันจะเพิ่มมัน ฉันคิดว่ารหัสเครื่องของรัฐจะละเอียดกว่าในกรณีนี้และเสร็จสิ้นภารกิจจะชนะลองดู
Roman Pokrovskij

1
จะเป็นการดีที่ได้เห็นสิ่งนี้ได้รับการอัปเดตสำหรับ. NET Core 2.2 เนื่องจากการจัดสรรในเครื่องสถานะ async ได้รับการปรับปรุงให้ดีขึ้นอย่างมาก
Tseng

1
@Tseng ฉันใช้เกณฑ์มาตรฐานบน. NET Core 2.2.0 เห็นได้ชัดว่าเวลาทั้งหมดแตกต่างกันเนื่องจากฮาร์ดแวร์ที่แตกต่างกัน แต่อัตราส่วนยังคงเท่าเดิม: วิธีการ | .NET Core 2.0.3 ค่าเฉลี่ย | .NET Core 2.2.0 ค่าเฉลี่ยเสร็จสมบูรณ์ | 100% | เสร็จสมบูรณ์ 100% รอ | 412.57% | 377.22% จากผลลัพธ์ | 520.72% | 590.89% Pragma | 378.37% | อัตราผลตอบแทน 346.64% | 27514.47% | 23602.38%
พายุ

10

ฉันรู้ว่านี่เป็นกระทู้เก่าและบางทีนี่อาจจะไม่มีผลที่ถูกต้องสำหรับการใช้งานทั้งหมด แต่สิ่งต่อไปนี้ใกล้เคียงที่สุดเท่าที่ฉันจะสามารถทำได้เพียงแค่โยน NotImplementedException เมื่อฉันยังไม่ได้ใช้วิธีการ โดยไม่ต้องแก้ไขลายเซ็นของวิธีการ หากเป็นปัญหาฉันยินดีที่จะทราบ แต่มันแทบไม่สำคัญสำหรับฉัน: ฉันใช้สิ่งนี้ในขณะที่อยู่ระหว่างการพัฒนาเท่านั้นดังนั้นวิธีการทำงานจึงไม่สำคัญทั้งหมด ถึงกระนั้นฉันก็ยินดีที่จะทราบว่าเหตุใดจึงเป็นความคิดที่ไม่ดีถ้าเป็นเช่นนั้น

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

นี่คือประเภทที่ฉันเพิ่มเพื่อให้เป็นไปได้

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

เช่นเดียวกับการอัปเดตคำตอบของ Stephen คุณไม่จำเป็นต้องเขียนTaskConstantsคลาสอีกต่อไปเนื่องจากมีวิธีการช่วยเหลือใหม่:

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

3
อย่าทำแบบนี้ การติดตามสแต็กจะไม่ชี้ไปที่โค้ดของคุณ ต้องโยนข้อยกเว้นเพื่อเริ่มต้นอย่างสมบูรณ์
Daniel B

1
Daniel B - ใช่คุณพูดถูกจริงๆ ฉันได้แก้ไขคำตอบของฉันเพื่อโยนข้อยกเว้นอย่างถูกต้อง
Matt

3

ในกรณีที่คุณเชื่อมโยงกับ Reactive Extension อยู่แล้วคุณสามารถทำได้:

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

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

ปฏิกิริยาและ async / await นั้นน่าทึ่งทั้งในและโดยตัวมันเอง แต่พวกเขาก็เล่นได้ดีด้วยกัน

สิ่งที่จำเป็น ได้แก่ :

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

3

มันอาจจะเกิด cs1998 ด้านล่าง

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

จากนั้นคุณสามารถปฏิรูปด้านล่าง

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



1

หากคุณไม่มีสิ่งที่ต้องรอให้ส่งคืน Task.FromResult

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

1

นี่คือทางเลือกบางอย่างขึ้นอยู่กับลายเซ็นวิธีของคุณ

    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

คุณสามารถวางคำหลัก async จากวิธีการและเพียงแค่ส่งคืนงาน

    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();
            }
        }));
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.