当前位置: 首页 > news >正文

C# 简单的异步文件下载器,可用于Unity

1.异步下载

using System.Collections;using System.Collections.Generic;using UnityEngine;using System;using System.IO;using System.Net.Http;using System.Threading;using System.Threading.Tasks;public class DownloadHelp:MonoBehaviour{privatevoidStart(){Test();}private CancellationTokenSource _cancellationTokenSource;public async TaskTest(){var downloader=newProgressableStreamDownloader();_cancellationTokenSource=newCancellationTokenSource();var progress=new Progress<(longdownloaded,long?total,doublepercent)>(p=>{Debug.Log($"已下载: {p.downloaded / 1024 / 1024}MB, "+$"总大小: {p.total / 1024 / 1024}MB, "+$"进度: {p.percent:F1}%");});await downloader.DownloadFileAsync("http://10.0.1.24/test.apk",//文件下载地址@"D:\Downloads\test.apk",//文件保存地址progress,_cancellationTokenSource.Token);_cancellationTokenSource?.Dispose();}publicvoidStopDownload(){_cancellationTokenSource?.Cancel();Debug.Log("已请求停止下载");}}public class ProgressableStreamDownloader{private readonly HttpClient _httpClient=newHttpClient();public async TaskDownloadFileAsync(string url,string savePath,IProgress<(longdownloaded,long?total,doublepercent)>progress=null,CancellationToken cancellationToken=default){try{Directory.CreateDirectory(Path.GetDirectoryName(savePath));using var response=await _httpClient.GetAsync(url,HttpCompletionOption.ResponseHeadersRead,cancellationToken);response.EnsureSuccessStatusCode();var totalBytes=response.Content.Headers.ContentLength;await using var contentStream=await response.Content.ReadAsStreamAsync();await using var fileStream=newFileStream(savePath,FileMode.Create,FileAccess.Write,FileShare.None,81920,true);var buffer=new byte[81920];longdownloadedBytes=0;intbytesRead;while((bytesRead=await contentStream.ReadAsync(buffer,0,buffer.Length,cancellationToken))>0){await fileStream.WriteAsync(buffer,0,bytesRead,cancellationToken);downloadedBytes+=bytesRead;// 报告进度progress?.Report((downloadedBytes,totalBytes,totalBytes.HasValue?(double)downloadedBytes/totalBytes.Value*100:0));}}catch(Exception e){Debug.LogError(e);}}}

2.线程下载

publicclassDownloadHelp:MonoBehaviour{privatevoidStart(){BeginDownloadThread();}voidBeginDownloadThread(){vardownloader=newThreadedDownloader();downloader.OnProgress+=(downloaded,total,percent)=>{vartxt=($"已下载:{downloaded/1024/1024}MB, "+$"总大小:{total/1024/1024}MB, "+$"进度:{percent:F1}%");/*var c = (int)percent; if (lastProcess != c) { Debug.Log(lastProcess); lastProcess = c; AndroidJNI.AttachCurrentThread(); //BackgroundServiceManager.Instance.UpdateProcess(txt, (int)percent); }*/};downloader.OnError+=(stage,ex)=>{Debug.LogError($"[{stage}]{ex.Message}");};downloader.OnCompleted+=()=>{Debug.Log("下载完成");//BackgroundServiceManager.Instance.StopBackgroundService();};downloader.StartDownload("http://10.0.1.24/test.apk",Application.persistentDataPath+"/down.apk");}publicvoidStopDownload(){downloader.StopDownload();Debug.Log("已请求停止下载");}}usingSystem;usingSystem.IO;usingSystem.Net.Http;usingSystem.Threading;usingUnityEngine;publicclassThreadedDownloader{privateThread_downloadThread;privatevolatilebool_isDownloading;privatevolatilebool_shouldStop;privatereadonlyobject_lockObject=newobject();publiceventAction<long,long?,double>OnProgress;publiceventAction<string,Exception>OnError;publiceventActionOnCompleted;publiceventActionOnCancelled;publicboolIsDownloading{get{lock(_lockObject){return_isDownloading;}}}publicvoidStartDownload(stringurl,stringsavePath){lock(_lockObject){if(_isDownloading){Debug.LogWarning("下载已在进行中");return;}_isDownloading=true;_shouldStop=false;}_downloadThread=newThread(()=>DownloadWorker(url,savePath)){IsBackground=true,Name="DownloadThread"};_downloadThread.Start();}publicvoidStopDownload(){lock(_lockObject){if(!_isDownloading){Debug.LogWarning("没有正在进行的下载");return;}_shouldStop=true;Debug.Log("已请求停止下载");}}privatevoidDownloadWorker(stringurl,stringsavePath){HttpClienthttpClient=null;StreamcontentStream=null;FileStreamfileStream=null;try{// 创建目录try{stringdirectory=Path.GetDirectoryName(savePath);if(!Directory.Exists(directory)){Directory.CreateDirectory(directory);}}catch(Exceptionex){InvokeOnError("创建目录",ex);return;}// 发起HTTP请求HttpResponseMessageresponse=null;try{httpClient=newHttpClient();httpClient.Timeout=TimeSpan.FromMinutes(30);vartask=httpClient.GetAsync(url,HttpCompletionOption.ResponseHeadersRead);response=task.Result;if(!response.IsSuccessStatusCode){thrownewHttpRequestException($"HTTP错误:{response.StatusCode}");}}catch(AggregateExceptionex){InvokeOnError("HTTP请求",ex.InnerException??ex);response?.Dispose();return;}catch(Exceptionex){InvokeOnError("网络请求",ex);response?.Dispose();return;}// 读取和写入文件try{vartotalBytes=response.Content.Headers.ContentLength;vartask=response.Content.ReadAsStreamAsync();contentStream=task.Result;try{fileStream=newFileStream(savePath,FileMode.Create,FileAccess.Write,FileShare.None,81920);}catch(Exceptionex){InvokeOnError("创建文件",ex);response?.Dispose();return;}varbuffer=newbyte[81920];longdownloadedBytes=0;intbytesRead;while((bytesRead=contentStream.Read(buffer,0,buffer.Length))>0){// 检查是否需要停止if(_shouldStop){InvokeOnCancelled();response?.Dispose();return;}// 写入文件try{fileStream.Write(buffer,0,bytesRead);downloadedBytes+=bytesRead;doublepercent=totalBytes.HasValue?(double)downloadedBytes/totalBytes.Value*100:0;InvokeOnProgress(downloadedBytes,totalBytes,percent);}catch(IOExceptionex){InvokeOnError("文件写入",ex);response?.Dispose();return;}catch(Exceptionex){InvokeOnError("写入数据",ex);response?.Dispose();return;}}// 下载完成InvokeOnCompleted();response?.Dispose();}catch(Exceptionex){InvokeOnError("文件操作",ex);response?.Dispose();return;}}finally{contentStream?.Close();fileStream?.Close();httpClient?.Dispose();lock(_lockObject){_isDownloading=false;}}}privatevoidInvokeOnProgress(longdownloaded,long?total,doublepercent){try{OnProgress?.Invoke(downloaded,total,percent);}catch(Exceptionex){Debug.LogError($"进度回调异常:{ex.Message}");}}privatevoidInvokeOnError(stringstage,Exceptionex){try{OnError?.Invoke(stage,ex);}catch(ExceptioncallbackEx){Debug.LogError($"错误回调异常:{callbackEx.Message}");}}privatevoidInvokeOnCompleted(){try{OnCompleted?.Invoke();}catch(Exceptionex){Debug.LogError($"完成回调异常:{ex.Message}");}}privatevoidInvokeOnCancelled(){try{OnCancelled?.Invoke();}catch(Exceptionex){Debug.LogError($"取消回调异常:{ex.Message}");}}publicvoidDispose(){StopDownload();if(_downloadThread!=null&&_downloadThread.IsAlive){_downloadThread.Join(TimeSpan.FromSeconds(5));}}}
http://www.jsqmd.com/news/476743/

相关文章:

  • 【Java从入门到入土】04:循环的尽头是递归?不,是Stream!
  • PPT小白必看:从Word到PPT的5分钟高效转换技巧(附字体版权避坑指南)
  • OpenClaw启动后,web控制面板无法登录,返回信息:Not Found
  • 相机标定及其高阶应用
  • SLDS 自营物流系统:FO 业务全解析
  • IMS网络实战:PCRF与AF的Rx接口消息流程详解(附典型消息示例)
  • 尝试用openclaw完成一个复杂的开发任务(持续更新)
  • cartographer 第一篇(官网2D Pure localization)
  • 瑞芯微开发板固件烧写:从变砖边缘到成功启动的实战指南
  • QGIS实战:5分钟搞定天地图道路数据下载与裁剪(附详细步骤)
  • JavaEE进阶10——SpringMVC小练习
  • 优惠券、满减、折扣同时生效,价格到底怎么算?
  • TI DSP 6678缓存优化全解析:如何用MAR寄存器提升实时性
  • “是我!”庆祝马里奥40年来始终坚持的匠心精神
  • 端口敲门技术深度对比:knockd vs SPA vs SDP,谁更适合你的服务器防护?
  • GIS数据处理必备:ArcMap中北京54与WGS84坐标系的区别与转换技巧
  • 计算机网络的定义和分类
  • EPLAN端子排自定义:从零搭建到高效维护
  • Electron 实战:将用户输入保存到本地文件 —— 基于 `fs.writeFileSync` 与 IPC 的安全写入方案
  • SenseVoice-small-ONNX效果展示:中日韩三国语言混合演讲识别连贯性测试
  • ThinkPad 满分维修评级:进步、妥协与公正性质疑
  • MacBook Air M5:性价比提升与开源支持困境
  • 2024年企业级网络架构实战:跨地域OSPF与BGP混合组网解析
  • 游戏开发必知:透视投影与正交投影的7个核心差异及适用场景
  • pure-ftpd安全配置全指南:从防火墙规则到虚拟用户权限管理
  • 通用文件读写封装:告别重复造轮子,让 C 语言文件操作更高效
  • 个人GPU福音!Kook Zimage真实幻想Turbo在独立游戏美术中的落地实践
  • FFC实战:如何用Fast Fourier Convolution提升图像修复效果(附代码示例)
  • Lattice Radiant 2024.2 从零到一:免费FPGA开发环境搭建与许可激活全攻略
  • 全栈可视化开发新选择 网易 CodeWave 开发效率拉满