получить значение обратного вызова из действия

#c#

#c#

Вопрос:

У меня есть эта функция с параметром action, после ее выполнения параметр в этой функции будет иметь значение. Есть ли способ для меня получить значение?

 public void DetailsAsync(string param1, string param2,Action<IList<Detail>> callback)
{
       //process happen here and will have a callback to produce the data for detail
}

public class DetailController:ApiController
{
  private IList<Detail> details;

  private DetailCompleted(IList<Detail> detail)
  {
    //now detail parameter has a value that I can use
    details = detail;
  }


  [HttpGet]
  public IList<Detail> GetDetails()
  {
    ServiceManager.DetailsAsync("param1","param2",detailsCompleted)

   //after ServiceManager.DetailsAsync it will go to return details
    return details;
  }
}
  

Когда я пробовал этот код, я установил точку останова в деталях возврата и точку останова в detailsCompleted, но случилось то, что когда я вызвал веб-api GetDatails, он сначала выполнит return details и сразу после этого он выполнит функцию detailsCompleted. вот почему в настоящее время я не могу получить значение.

Комментарии:

1. В целом, для меня это выглядит как правильный шаблон обратного вызова, но у меня есть некоторые опасения по поводу того, почему некоторые вещи содержат слово «асинхронный» в своих названиях. Я подозреваю, что может отсутствовать какой-то важный код. Можете ли вы поделиться кодом внутри DetailsAsync ?

2. @BradleyUffner к сожалению, у меня нет кода для этого, все, что я знаю, это параметр, необходимый для использования DetailsAsync, но я думаю, что они вызывают службу с асинхронностью внутри нее.

3. @BradleyUffner на самом деле, когда устанавливается точка останова в DetailCompleted, в detail появляется нужная мне запись, но поскольку сначала выполнялась «return details», а не DetailCompleted, моя переменная details не получает записей.

4. Это то, что я подозревал. Вам нужен какой-то способ отложить выполнение return инструкции до тех пор, пока DetailCompleted не будет получено сообщение о том, что результат получен. Semaphore Или TaskCompletionSource могло бы сработать, но может быть немного сложно настроить. Реальная проблема заключается в том, что DetailsAsync реализован с плохим шаблоном асинхронности.

5. @BradleyUffner насколько я понимаю, Semaphore или TaskCompletionSource могут быть реализованы в void, как DetailAsync, верно? поскольку у меня нет доступа к коду DetailsAsync

Ответ №1:

Возвращаемое значение a Action<T> по умолчанию равно void. Если вы хотите возвращаемое значение, вы должны использовать Func<T,TResult>

https://learn.microsoft.com/en-us/dotnet/api/system.func-2?view=netframework-4.7.2

Комментарии:

1. Спасибо и да, я согласен с вами, проблема в том, что мне разрешено создавать только мою собственную функцию веб-api и использовать объект ServiceManager для получения записей, и, как я уже упоминал, я также не понимаю, почему сначала были заданы детали возврата, а затем функция DetailCompleted.

2. Когда вы говорите «по умолчанию void», вы имеете в виду, что оно когда-либо может быть не-void?

3. Он использует обратный вызов для «возврата» данных. Для того, чтобы это сработало, нет необходимости преобразовывать метод в Func .

4. @pteberf это не может быть не-void, я думаю, я на самом деле хотел сказать «по определению»

5. @BradleyUffner Да, я думаю, вы правы, я неправильно понял вопрос. Ответ pteberf правильный

Ответ №2:

Я думаю, проблема здесь в том, что DetailsAsync() , как подразумевается под name, является асинхронным, и вы возвращаете детали, прежде чем дождаться результатов DetailsAsync() . Итак, вы должны await это сделать, но из-за DetailsAsync возврата void вы не можете.

Таким образом, вы могли бы обернуть DetailsAsync в задачу и .Wait() для нее, но это своего рода отстой, потому что вы заблокируете вызывающий поток.

 [HttpGet]
public IList<Detail> GetDetails()
{
    Task.Run(() => 
        ServiceManager.DetailsAsync("param1", "param2", detailsCompleted)
    ).Wait();
    return details;
}
  

Комментарии:

1. Я прошу прощения за не очень четкое объяснение, ServiceManager. DetailAsync является недействительным, и внутри него, я думаю, они вызывают функцию async, вот почему я думаю, что они назвали ее DetailAsync

2. Ну, если они вызывают асинхронную функцию внутри и ожидают ее, скорее всего, DetailsAsync тоже асинхронен. async методы также могут возвращать void .

3. @pteberf но когда я попытался реализовать приведенный вами код, он выдает ошибку «не удается дождаться void»

4. Если DetailsAsync реализовано, как я подозреваю, ожидание этого не поможет. Каково происхождение DetailsAsync ? Является ли это частью какого-то общедоступного пакета или это внутренне написанный код, к которому у вас просто нет доступа?

5. @BradleyUffner это внутренне написанный код, к которому у меня нет доступа.

Ответ №3:

Из-за того, как DetailsAsync написано, вам понадобится какая-то система сигнализации, чтобы приостановить выполнение GetDetails до тех пор, пока не будет запущен обратный вызов. Есть несколько вариантов, но я выбираю AutoResetEvent , потому что с ним довольно просто работать и понимать.

(Я изменил некоторые возвращаемые типы только для того, чтобы мне не пришлось создавать поддельные классы, соответствующие вашему коду)

 public class DetailController
{
    private IList<int> details;
    private AutoResetEvent callbackSignal = new AutoResetEvent(false);

    private void DetailCompleted(IList<int> detail)
    {
        details = detail;
        callbackSignal.Set();
    }    

    public IList<int> GetDetails()
    {    
        ServiceManager.DetailsAsync("param1", "param2", DetailCompleted);
        callbackSignal.WaitOne();
        return details;
    }
}
  

callbackSignal.WaitOne(); будет блокироваться до тех пор, пока не будет «подан сигнал». В методе обратного вызова callbackSignal.Set(); отправляет сигнал, сообщающий всему, ожидающему события, что теперь можно продолжить.

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


Если вы предпочитаете работать с более современным шаблоном async / await , вы могли бы обернуть access в DetailsAsync access в метод, который Task возвращает и использует TaskCompletionSource для организации обратного вызова и возвращаемых значений.

 public class DetailController
{
    public async Task<IList<int>> GetDetails()
    {
        var details = await ServiceWrapper.GetDetails();
        return details;
    }
}

public static class ServiceWrapper
{
    public static Task<IList<int>> GetDetails()
    {
        var tcs = new TaskCompletionSource<IList<int>>();
        ServiceManager.DetailsAsync("param1", "param2", (IList<int> details) =>
            {
                tcs.SetResult(details);
            });
        return tcs.Task;
    }
}
  

Комментарии:

1. Спасибо за пример кода, я пробовал, но это занимает целую вечность, до сих пор он все еще работает.

2. Я добавил пример TaskCompletionSource шаблона, который может лучше подойти для вашего конкретного случая.

3. ууууууууууу!!!!, большое спасибо, Брэдли, твой код для TaskCompletionSource действительно сработал!!! это заняло у меня целый день!!! еще раз спасибо!