在 Unity 开发中,将协程与 C# 的 async/await
机制结合,可以显著提高代码的可读性与维护性,并且支持返回值。
异步与协程结合在数据请求中的优势
-
提高代码可读性:
- 与传统协程相比,
async/await
更接近同步逻辑,减少嵌套和复杂控制流。
- 与传统协程相比,
-
支持返回值:
- 协程本身不能直接返回值,而通过异步与协程结合,可以轻松的在当前帧返回请求结果,而不是等待回调。
-
便于扩展:
- 例如,开发者可以轻松地在
Get
和Post
方法中加入身份验证、错误重试等逻辑,而无需修改协程的底层实现。
- 例如,开发者可以轻松地在
协程与异步结合的实现
核心方法: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
的结果转换为异步任务。 -
关键逻辑:
- 创建
TaskCompletionSource
对象,用于管理异步任务的完成状态。 - 启动协程执行实际的 HTTP 请求。
- 使用
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);
}
-
协程逻辑:
- 使用
HTTPRequest
发起网络请求。 - 等待请求完成,期间每帧检查请求状态。
- 根据请求状态设置结果。
- 使用
-
与异步任务的结合:
- 在协程末尾,通过
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);}
}