Bootstrap

unity中:Unity 中异步与协程结合实现线程阻塞的http数据请求

在 Unity 开发中,将协程与 C# 的 async/await 机制结合,可以显著提高代码的可读性与维护性,并且支持返回值。


异步与协程结合在数据请求中的优势

  1. 提高代码可读性

    • 与传统协程相比, async/await 更接近同步逻辑,减少嵌套和复杂控制流。
  2. 支持返回值

    • 协程本身不能直接返回值,而通过异步与协程结合,可以轻松的在当前帧返回请求结果,而不是等待回调。
  3. 便于扩展

    • 例如,开发者可以轻松地在 GetPost 方法中加入身份验证、错误重试等逻辑,而无需修改协程的底层实现。

协程与异步结合的实现

核心方法:GetOrPostDataA
private async Task<string> GetOrPostDataA(string URL, Dictionary<string, string> body = null)
{
    var taskCompletionSource = new TaskCompletionSource<string>();
    var result = "";

    StartCoroutine(ExecuteRequest(URL, body, result, taskCompletionSource));

    // 等待协程完成
    return await taskCompletionSource.Task;
}
  • 功能:将协程 ExecuteRequest 的结果转换为异步任务。

  • 关键逻辑

    1. 创建 TaskCompletionSource 对象,用于管理异步任务的完成状态。
    2. 启动协程执行实际的 HTTP 请求。
    3. 使用 await taskCompletionSource.Task 挂起当前方法,等待协程完成。
  • 异步与协程结合点

    • TaskCompletionSource 是桥梁,协程通过 SetResult 通知异步任务完成,从而实现协程与 async/await 的结合。

协程实现:ExecuteRequest
private IEnumerator ExecuteRequest(string URL, Dictionary<string, string> body, string result, TaskCompletionSource<string> taskCompletionSource)
{
    // 创建并发送请求
    var request = new HTTPRequest(new Uri(URL), body == null ? HTTPMethods.Get : HTTPMethods.Post).Send();

    if (body != null)
    {
        MultipartFormDataStream data = new MultipartFormDataStream();
        foreach (var variable in body)
        {
            data.AddField(variable.Key, variable.Value);
        }
    }

    // 等待请求完成
    while (request.State < HTTPRequestStates.Finished)
    {
        yield return null;
    }

    // 检查请求结果
    switch (request.State)
    {
        case HTTPRequestStates.Finished:
            if (request.Response.IsSuccess)
            {
                result = request.Response.DataAsText;
            }
            else
            {
                result = $"Server error: {request.Response.StatusCode}";
            }
            break;

        case HTTPRequestStates.Error:
            result = $"Error: {request.Exception?.Message ?? "Unknown error"}";
            break;

        default:
            result = "Request failed or timed out.";
            break;
    }

    // 通知异步任务完成
    taskCompletionSource.SetResult(result);
}
  • 协程逻辑

    1. 使用 HTTPRequest 发起网络请求。
    2. 等待请求完成,期间每帧检查请求状态。
    3. 根据请求状态设置结果。
  • 与异步任务的结合

    • 在协程末尾,通过 taskCompletionSource.SetResult 将结果传递给异步任务,从而完成任务。

使用示例

1. GET 请求示例

async void PerformGetRequest()
{
    string url = "https://example.com/api/resource";
    string response = await HttpHelper.Instance.Get(url);
    Debug.Log("GET Response: " + response);
}

2. POST 请求示例

async void PerformPostRequest()
{
    string url = "https://example.com/api/resource";
    Dictionary<string, string> parameters = new Dictionary<string, string>
    {
        { "username", "testuser" },
        { "password", "123456" }
    };
    string response = await HttpHelper.Instance.Post(url, parameters);
    Debug.Log("POST Response: " + response);
}

完整代码如下

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Best.HTTP;
using Best.HTTP.Request.Upload.Forms;
using UnityEngine;

public sealed class HttpHelper : MonoBehaviour
{
    // 单例实例
    private static HttpHelper _instance;

    // 线程锁,确保线程安全
    private static readonly object Lock = new object();

    // 公共访问接口
    public static HttpHelper Instance
    {
        get
        {
            lock (Lock)
            {
                if (_instance == null)
                {
                    // 查找是否有已有的实例
                    _instance = FindObjectOfType<HttpHelper>();

                    if (_instance == null)
                    {
                        // 创建新的实例
                        GameObject singletonObject = new GameObject();
                        _instance = singletonObject.AddComponent<HttpHelper>();
                        singletonObject.name = typeof(HttpHelper).ToString();

                        // 确保单例不会被销毁
                        DontDestroyOnLoad(singletonObject);
                    }
                }
                return _instance;
            }
        }
    }

    // 防止通过构造函数创建实例
    private HttpHelper() { }

    public async Task<string> Get(string url, bool useToken = true)
    {
        return await GetOrPostDataA(url);
    }

    public async Task<string> Post(string url, Dictionary<string,string> listParam = null, bool useToken = true)
    {
        return await GetOrPostDataA(url, listParam);
    }

    private async Task<string> GetOrPostDataA(string URL, Dictionary<string,string> body = null)
    {
        var taskCompletionSource = new TaskCompletionSource<string>();
        var result = "";

        StartCoroutine(ExecuteRequest(URL, body, result, taskCompletionSource));

        // 等待协程完成
        return await taskCompletionSource.Task;
    }

    private IEnumerator ExecuteRequest(string URL, Dictionary<string,string> body, string result, TaskCompletionSource<string> taskCompletionSource)
    {
        string status = "";

        // 创建并发送请求
        var request = new HTTPRequest(new Uri(URL), body == null ? HTTPMethods.Get : HTTPMethods.Post).Send();

        if (body != null)
        {
            MultipartFormDataStream data = new MultipartFormDataStream();
            foreach (var variable in body)
            {
                data.AddField(variable.Key, variable.Value);
            }
        }

        // 等待请求完成
        while (request.State < HTTPRequestStates.Finished)
        {
            yield return null;
        }

        // 检查请求结果
        switch (request.State)
        {
            case HTTPRequestStates.Finished:
                if (request.Response.IsSuccess)
                {
                    Debug.Log(request.Response.DataAsText);
                    // // 将响应数据保存到 E:/Data.json
                    // try
                    // {
                    //     string filePath = "E:/Data.json";
                    //     File.WriteAllText(filePath, request.Response.DataAsText);
                    //     Debug.Log($"Response data saved to {filePath}");
                    // }
                    // catch (Exception ex)
                    // {
                    //     Debug.LogError($"Error saving response data to file: {ex.Message}");
                    // }
                    
                    result = request.Response.DataAsText;
                }
                else
                {
                    status = string.Format(
                        "Request finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2}",
                        request.Response.StatusCode,
                        request.Response.Message,
                        request.Response.DataAsText);
                    Debug.LogWarning(status);
                    
                    result = status;
                }
                break;

            case HTTPRequestStates.Error:
                status = "Request Finished with Error! " + (request.Exception != null
                    ? (request.Exception.Message + "\n" + request.Exception.StackTrace)
                    : "No Exception");
                Debug.LogError(status);
                
                result = status;
                break;

            case HTTPRequestStates.Aborted:
                status = "Request Aborted!";
                Debug.LogWarning(status);
                
                result = status;
                break;

            case HTTPRequestStates.ConnectionTimedOut:
                status = "Connection Timed Out!";
                Debug.LogError(status);
                
                result = status;
                break;

            case HTTPRequestStates.TimedOut:
                status = "Processing the request Timed Out!";
                Debug.LogError(status);
                
                result = status;
                break;
        }

        // 通知任务完成
        taskCompletionSource.SetResult(result);
    }
}

;