【问题标题】:How to wait for a callback before returning如何在返回前等待回调
【发布时间】:2018-01-19 21:35:46
【问题描述】:

我正在尝试将代码从 .Net 移植到 Unity C#,但我被困在包括回调在内的语法上。

基本上,我不得不将 .Net 'HttpClient' 库替换为 this one,也称为 'HttpClient'。但“Get”语法不一样,它使用回调。我对 C# 和 Http 查询很陌生,不知道如何处理这种语法以获得预期的回报。

.Net写的原函数:

internal static JObject GetToBackOffice(string action)
{
    var url = backOfficeUrl;
    url = url + action;

    HttpClient httpClient = new HttpClient();

    var response =
        httpClient.GetAsync(url
        ).Result;
    if (response.StatusCode == System.Net.HttpStatusCode.OK)
    {
        var idProcess = Newtonsoft.Json.Linq.JObject.Parse(response.Content.ReadAsStringAsync().Result);

        return idProcess;
    }
    else
        return null;

我为 Unity 编写的 C# 代码:

internal class Utils
{ 
    internal static JObject GetToBackOffice(string action)
    {
        var url = backOfficeUrl;
        url = url + action;

        HttpClient httpClient = new HttpClient();

        JObject idProcess = new JObject();

        httpClient.GetString(new Uri(url),
            (response) =>
            {
                // Raised when the download completes
                if (response.StatusCode == System.Net.HttpStatusCode.OK)
                {
                    idProcess = Newtonsoft.Json.Linq.JObject.Parse(response.Data);
                }
                else
                {
                    idProcess = null;
                }
            });
        // Here I would like to wait for the response, so that idProcess is filled with the received data before returning
        return idProcess;
    } 

public class Action
{
     public bool SendData(string id, string secretKey, FakeData data)
    {
        var idProcess = Utils.GetToBackOffice(String.Format("Events/{0}/infos",id));
        //...I do then something with idProcess
        //Currently, when I use idProcess here, it is still empty since the GetString response hasn't been received yet when this line is executed

        return true;
    }
}

public class EventHubSimulator : MonoBehaviour
{
    void Start()
    {
        //Fill the parameters (I skip the details)
        string oId = ...;
        string secretKey = ...;
        var vh = ...;

        Action action = new Action();
        action.SendData(oId, secretKey, vh);
    }
}

我的问题是,在 GetToBackOffice 函数之后,我的代码直接将“idProcess”用于其他内容,但该对象为空,因为尚未收到响应。我想在我的函数返回之前等待响应。

我希望我已经足够清楚了。我知道已经发布了类似的问题,但找不到针对我的具体问题的解决方案。


编辑:

最后我按照 Nain 的建议使用了协程,但无法按照他所说的方式得到我所期望的。这种方式似乎可行(尽管这可能不是一个好方法)。

public class EventHubSimulator : MonoBehaviour
    {
        void Start()
        {
            //Fill the parameters (I skip the details)
            string oId = ...;
            string secretKey = ...;
            var vh = ...;

            Utils utils = new Utils();
            StartCoroutine(utils.SendData(oId, secretKey, vh));
        }
    }

public class Utils: MonoBehaviour
{
    private const string backOfficeUrl = "http://myurl/api/";

    public CI.HttpClient.HttpResponseMessage<string> response;

    public IEnumerator SendData(string id, string secretKey, FakeData data)
    {
        response = null;
        yield return GetToBackOffice(String.Format("Events/{0}/infos", id)); //Make a Http Get request
        //The next lines are executed once the response has been received
        //Do something with response
        Foo(response);
    }

    IEnumerator GetToBackOffice(string action)
    {
        var url = backOfficeUrl;
        url = url + action;

        //Make a Http Get request
        HttpClient httpClient = new HttpClient();
        httpClient.GetString(new Uri(url), (r) =>
        {
            // Raised when the download completes
            if (r.StatusCode == System.Net.HttpStatusCode.OK)
            {
                //Once the response has been received, write it in the global variable
                response = r;
                Debug.Log("Response received : " + response);
            }
            else
            {
                Debug.Log("ERROR =============================================");
                Debug.Log(r.ReasonPhrase);
                throw new Exception(r.ReasonPhrase);
            }
        });

        //Wait for the response to be received
        yield return WaitForResponse();
        Debug.Log("GetToBackOffice coroutine end ");
    }

    IEnumerator WaitForResponse()
    {
        Debug.Log("WaitForResponse Coroutine started");
        //Wait for response to become be assigned
        while (response == null) 
        {
            yield return new WaitForSeconds(0.02f);
        }
        Debug.Log("WaitForResponse Coroutine ended");
    }
}    

【问题讨论】:

  • 您能发布如何调用GetToBackOffice() 方法吗?是来自 Update() 还是其他地方?
  • @S.Fragkos 从 Start() 调用
  • 这会有所帮助吗:根据this 问题的公认答案,您可以定位.Net 4.5 并使用使用任务的“真实”HttpClient,而不是等待。
  • @PeterBons 实际上这是我尝试的第一件事。不幸的是,即使以 .Net 4.5 为目标,我也无法让 HttpClient 工作。它不是 c# 项目的引用,添加 dll 并没有解决问题。关于这个主题有this thread,所以 Unity 开发团队意识到了这个问题,但似乎没有人设法让它真正起作用。我想在花了两天时间尝试使它以这种方式工作后,我会尝试另一种方式。
  • 好的,但是您可以在 .Net 4.5 的 Unity 中使用 async/await 不是吗?如果是这样,我可能有一个解决方案。

标签: c# .net http unity3d


【解决方案1】:

当 Nain 作为答案提交时,一种解决方案是轮询完成。如果您不想进行轮询,可以使用TaskCompletionSourceThis Q&A 更深入地探讨了原因和方法。

你的代码可以这样写:

async Task CallerMethod()
{
    JObject result = await GetToBackOffice(...);
    // Do something with result
}

internal static Task<JObject> GetToBackOffice(string action)
{
    var tsc = new TaskCompletionSource<JObject>();
    var url = backOfficeUrl;
    url = url + action;

    HttpClient httpClient = new HttpClient();

    JObject idProcess = new JObject();

    httpClient.GetString(new Uri(url),
        (response) =>
        {
            // Raised when the download completes
            if (response.StatusCode == System.Net.HttpStatusCode.OK)
            {
                tsc.SetResult(Newtonsoft.Json.Linq.JObject.Parse(response.Data));
            }
            else
            {
                tsc.SetResult(null);
            }
        });

    return tsc.Task;
}

另请参阅thismsdn 博文的异步方法调用协程并等待完成部分。

注意 任务和异步/等待支持仅在 Unity 中以 beta functionality 的形式提供。另请参阅this 帖子。

【讨论】:

  • 它是 Unity 和 Mono。没有 Task 类,也没有可用的异步。
  • @Everts,根据OP的评论,他链接到stevevermeulen.com/index.php/2017/09/…。但更新了我的答案以使其更清楚
  • @PeterBons 感谢您的回答。最后我放弃了使用 .Net 4.5 有几个原因:我无法让 System.Threading.Task 正常工作,无论如何,这个 .Net 版本使 Visual Studio 调试器无法使用(在启动时崩溃),使我无法调试我的Unity 项目对我来说是不可能的([似乎已修复] (issuetracker.unity3d.com/issues/…)),但我现在无法真正更新)。我想我终于设法通过协程得到了我想要的东西(我用它编辑了我的帖子)。
【解决方案2】:

像这样写一个Co例程

  //Class scope variable is neede to hold Resopnce other wise it will 
  //be destroied as soon as function is ended
  ResponceType  response;
  IEnumerator WaitForResponce(ResponceType  response)
  {
        this.response = response;
        while(this.response.Data == null)
        yield return new WaitForSeconds (0.02f);
   //do what you want here

  }

并调用协程

 httpClient.GetString(new Uri(url),
    (response) =>
    {
        // Raised when the download completes
        if (response.StatusCode == System.Net.HttpStatusCode.OK)
        {
            //idProcess = Newtonsoft.Json.Linq.JObject.Parse(response.Data);
            StartCoroutine(WaitForResponce(responce));
        }
        else
        {
            idProcess = null;
        }
    });

【讨论】:

  • 谢谢,这看起来很棒,我正在尝试!
  • 我在实现时遇到了麻烦,因为这个方法的类是静态的,而 StartCoroutine 必须从一个实例中调用,我正在尝试创建一个类的单例,我会告诉你的。跨度>
  • 一个问题:在你的代码中,当响应回调被触发时,响应已经被接收到了,所以 WaitForResponce 协程不应该在那里启动,对吧?
  • 不应该从这里开始这个协程将等待数据加载。类似的概念用于从 web 加载纹理,在收到响应后需要一些时间来下载
  • 好吧,现在我不明白如何返回 idProcess 值。问题是我然后在调用类var idProcess = Utils.GetToBackOffice(String.Format("Events/{0}/infos",id)); 中使用它。所以我看不到如何将 idProcess 从协程发送到 Action 类。我完成了我的帖子,以便更清楚。
【解决方案3】:

如果 GetString 方法返回一个 Task 对象,那么您可以使用 Wait() 方法让 Task 在返回结果之前完成处理。

 var task = httpClient.GetString({
    impl here..
 });
 
 task.Wait();
 return idProcess;
 
 

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2011-01-06
  • 2016-09-03
  • 2020-04-17
  • 1970-01-01
  • 2014-11-26
相关资源
最近更新 更多