在 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);
}
}