Як змусити асинхронний метод повернути значення?


76

Я знаю, як зробити методи Async, але кажу, що у мене є метод, який виконує багато роботи, а потім повертає логічне значення?

Як повернути логічне значення для зворотного виклику?

Уточнення :

public bool Foo(){
    Thread.Sleep(100000); // Do work
    return true;
}

Я хочу мати можливість зробити це асинхронним.

Відповіді:


58

Є кілька способів зробити це ... найпростіший - це, щоб метод асинхронізації також виконував подальшу операцію. Іншим популярним підходом є передача зворотного виклику, тобто

void RunFooAsync(..., Action<bool> callback) {
     // do some stuff
     bool result = ...

     if(callback != null) callback(result);
}

Іншим підходом було б викликати подію (з результатом у даних event-args), коли операція асинхронізації завершена.

Крім того, якщо ви використовуєте TPL, ви можете використовувати ContinueWith:

Task<bool> outerTask = ...;
outerTask.ContinueWith(task =>
{
    bool result = task.Result;
    // do something with that
});

4
Гарний простий приклад підходу зворотного виклику
DRapp

138

З C # 5.0 ви можете вказати метод як

public async Task<bool> doAsyncOperation()
{
    // do work
    return true;
}

bool result = await doAsyncOperation();

25
Для тих, хто цікавиться, отримати результати, які ви б використалиbool result = await doAsyncOperation();
Гордон Такер

4
Кожного разу, коли ви збираєтеся використовувати ключове слово "await", воно повинно знаходитися всередині методу, позначеного як мінімум "async". Спочатку я, хоча тільки робочий метод повинен був бути позначений таким чином. Наприклад, навіть якщо ваш метод виклику не поверне нічого, ви повинні назвати його "async void MyCallerMethod"
Mike K

5
@KingOfHypocrites return await Task.FromResult(true)відхилить це попередження.
Боян Комазец

2
Це насправді нічого асинхронно не зробить, якщо ви awaitщось не знайдете в тілі методу. Повернення Task.FromResult(true)не змінює цього. Тіло методу синхронно працює на потоці абонента до першого очікування.
Дрю Ноукс

2
Мені здається, що це спрацьовує, рядок "bool result = ..." також повинен знаходитися в межах асинхронного методу, тому я не думаю, що це насправді відповідає на запитання
nuander

4

Напевно, найпростіший спосіб зробити це - створити делегата, а потім BeginInvoke, зачекавши якийсь час у майбутньому, і EndInvoke.

public bool Foo(){
    Thread.Sleep(100000); // Do work
    return true;
}

public SomeMethod()
{
    var fooCaller = new Func<bool>(Foo);
    // Call the method asynchronously
    var asyncResult = fooCaller.BeginInvoke(null, null);

    // Potentially do other work while the asynchronous method is executing.

    // Finally, wait for result
    asyncResult.AsyncWaitHandle.WaitOne();
    bool fooResult = fooCaller.EndInvoke(asyncResult);

    Console.WriteLine("Foo returned {0}", fooResult);
}

AsyncWaitHandle - це WaitHandle. Він не має методу WaitOne (). Ви повинні кинути його до того, що він має, щоб дочекатися завершення.
the_drow

1
@the_drow: Можливо, ви захочете поглянути на цей приклад: msdn.microsoft.com/en-us/library/… . Також подивіться на документацію до WaitHandle.WaitOneметоду: msdn.microsoft.com/en-us/library/58195swd.aspx
Джим Мішель,

2
Або ви можете просто вставити цей код у програму на C # і протестувати його. Здавалося, у мене це вийшло.
Jim Mischel

3

Використовуйте BackgroundWorker. Це дозволить вам отримувати зворотні дзвінки після завершення та дозволить відстежувати прогрес. Ви можете встановити значення Result в аргументах події на результуюче значення.

    public void UseBackgroundWorker()
    {
        var worker = new BackgroundWorker();
        worker.DoWork += DoWork;
        worker.RunWorkerCompleted += WorkDone;
        worker.RunWorkerAsync("input");
    }

    public void DoWork(object sender, DoWorkEventArgs e)
    {
        e.Result = e.Argument.Equals("input");
        Thread.Sleep(1000);
    }

    public void WorkDone(object sender, RunWorkerCompletedEventArgs e)
    {
        var result = (bool) e.Result;
    }

Працівник, який працює в цій галузі, тут непродуктивний. Ви не знаєте AutoResetEvent / ManualResetEvent?
the_drow

1
@the_drow я з цим не погоджуюсь; backgroundWorker та RunWorkerCompleted - тут цілком хороший підхід
Марк Гравелл

@the_drow - ні, ні. Побачу, що я можу про це дізнатись. Але якби ви могли пояснити, чому ви вважаєте, що це контрпродуктивно, я хотів би зрозуміти.
NerdFury

Це контрпродуктивно, оскільки ви занадто багато кодуєте для того, що насправді не потребує такого типу коду. Фоновий працівник - це те, що ви робите, коли у вас тривалий процес, який звітує перед інтерфейсом користувача. Метод асинхронізації в c # не повинен повідомляти про хід, а лише про завершення, оскільки інші можуть чекати його завершення (для цього призначені Auto / MenualResetEvent). Шаблон методу асинхронізації - відома ідіома асинхронного виклику методу. Єдиний дійсний підхід - це те, що я запропонував, або те, що запропонував @Marc.
the_drow

@MarcGravell: Не кожна річ - це цвях. Це може спрацювати. Це семантично неправильно.
the_drow

1

Можливо, ви можете спробувати розпочати виклик делегата, який вказує на ваш метод так:



    delegate string SynchOperation(string value);

    class Program
    {
        static void Main(string[] args)
        {
            BeginTheSynchronousOperation(CallbackOperation, "my value");
            Console.ReadLine();
        }

        static void BeginTheSynchronousOperation(AsyncCallback callback, string value)
        {
            SynchOperation op = new SynchOperation(SynchronousOperation);
            op.BeginInvoke(value, callback, op);
        }

        static string SynchronousOperation(string value)
        {
            Thread.Sleep(10000);
            return value;
        }

        static void CallbackOperation(IAsyncResult result)
        {
            // get your delegate
            var ar = result.AsyncState as SynchOperation;
            // end invoke and get value
            var returned = ar.EndInvoke(result);

            Console.WriteLine(returned);
        }
    }

Потім використовуйте значення методу, який ви надіслали як AsyncCallback, щоб продовжити.


-3

Вам слід використовувати EndXXX вашого асинхронного методу, щоб повернути значення. EndXXX повинен почекати, поки не буде результату, використовуючи WaitHandle IAsyncResult, а потім повернути зі значенням.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.