Подтвердить что ты не робот

Нет предупреждения, когда я забуду `await` при вызове метода интерфейса

Рассмотрим:

using System.Threading.Tasks;

class Program
{
    static void Main(string[] args)
    {
        C c = new C();
        c.FooAsync(); // warning CS4014: 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.
        ((I)c).FooAsync(); // No warning
    }
}

class C : I
{
    public async Task FooAsync()
    {
    }
}

interface I
{
    Task FooAsync();
}

Если я вызываю метод async непосредственно на объект c, я получаю предупреждение о компиляторе. Там потенциально ошибка здесь, поэтому я рад за предупреждение.

Однако, если я делаю тот же вызов по методу интерфейса, я не получаю предупреждения. Было бы легко пропустить пропущенную ошибку в этом коде.

Как я могу гарантировать, что я не ошибаюсь? Есть ли образец, который я могу применить для защиты себя?

4b9b3361

Ответ 1

Main не является асинхронным, поэтому он не может использовать await. Кажется, это немного путает сообщения компилятора. Если вы поместили вызовы в реальный метод асинхронного вызова,

static void Main(string[] args)
{
    Task.Run(async () =>
                       {
                           C c = new C();
                           c.FooAsync();
                           ((I) c).FooAsync();
                       });
}

... оба будут предупреждать.

Строка 10: поскольку этот вызов не ожидается, выполнение текущего метода продолжается до завершения вызова. Рассмотрите возможность применения оператора "ожидание" к результату вызова.
Строка 11: поскольку этот вызов не ожидается, выполнение текущего метода продолжается до завершения вызова. Рассмотрите применение оператора "ожидание" к результату вызова.

EDIT: Кажется, что все методы, возвращающие Task внутри асинхронных методов, будут предупреждать, если вы не ждете или не назначаете их; обратите внимание, что мы работаем с интерфейсом, который даже не упоминает async;

interface I
{
    Task FooAsync();
}

static void Main(string[] args)
{
    I i = null;

    i.FooAsync();             // Does not warn
    // await i.FooAsync();    // Can't await in a non async method
    var t1 = i.FooAsync();    // Does not warn

    Task.Run(async () =>
    {
       i.FooAsync();          // Warns CS4014
       await i.FooAsync();    // Does not warn
       var t2 = i.FooAsync(); // Does not warn
    });
}

Ответ 2

Я бы рискнул сказать, что невозможно сделать это предупреждение на уровне компиляции. чтобы поддержать мою точку зрения, посмотрите на этот пример:

interface I
{
    Task Foo();
}

class A : I
{
    public Task Foo()
    {
    }
}

class B : I
{
    public async Task Foo()
    {
    }
}

public class Program
{
    private static void Main(string[] args)
    {
        I i;

        if (Console.ReadLine() == "1")
        {
            i = new A();
        }
        else i = new B();

        i.Foo();
    }
}

Ваша первая мысль может быть: Но это абсурдная ситуация. Но некоторые шаблоны проектирования (пример - метод factory), используя механизмы, которые производят производные классы очень динамичным способом.

Итак, как VS может узнать, является ли метод асинхронным или нет?

Ответ 3

Логика для этого предупреждения выглядит следующим образом:

  • в методе async, предупреждать при вызове метода Task -returning, но результат игнорируется
  • в обычном (не async) методе, предупреждать при вызове метода Task -returning async, но результат игнорируется

Например, посмотрите на этот (бессмысленный) код:

Task NonAsyncMethod()
{
    AsyncMethod(); // warnig
    NonAsyncMethod(); // no warning

    return null; // to make the code compile
}

async Task AsyncMethod()
{
    AsyncMethod(); // warning
    NonAsyncMethod(); // warning
}

Вот почему вы не получаете предупреждение с интерфейсом: метод интерфейса не (и не может) быть помечен как async.

Я думаю, что причина в том, что в старом, pre- async коде, он распространен, например, называть task.ContinueWith() и игнорировать его результат. Если бы предупреждение также сообщалось в этом случае, относительно большое количество старого правильного кода внезапно стало бы предупреждением.

Предупреждение должно выводиться, если существует большая вероятность ошибки. Я думаю, что случаи, о которых сообщают, гораздо чаще становятся ошибками, чем случаи, которых нет. Поэтому для меня это имеет смысл.

Если вы хотите убедиться, что не допустили ошибку, будьте осторожны при вызове методов Task -returning из кода <async.

Ответ 4

Я думаю, вы можете слишком много просить здесь.

interface I
{
    void Foo();
}

class C {} // does not implement I

class Program
{
    static void Main(string[] args)
    {
        C c = new C();
        ((I)c).Foo(); // Generates no compiler warning
    }
}

Тем не менее, кастинг происходит во время выполнения, и во время выполнения (или в CIL) нет ничего такого, как async. Компилятор преобразует async Task Foo() в Task Foo(), реализованный как state-machine из ко-подпрограмм.