C# FileStream实现多线程断点续传

一、前言

网上有许多的多线程断点续传操作,但总是写的很云里雾里,或者写的比较坑长。由于这几个月要负责公司的在线升级项目,所以正好顺便写了一下

代码如下:

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

namespace TestCenter
{
 class Program
 {
  static void Main(string[] args)
  {
   string LocalSavePath = @"E:\Test\TestFile\Local\1.msi"; //本地目标文件路径

   FileInfo SeverFilePath = new FileInfo(@"E:\Test\TestFile\Server\1.msi"); //服务器待文件路径
   long FileLength = SeverFilePath.Length; //待下载文件大小

   Console.WriteLine("Start Configuration");
   int PackCount = 0; //初始化数据包个数

   long PackSize = 1024000; //数据包大小

   if (FileLength % PackSize > 0)
   {
    PackCount = (int)(FileLength / PackSize) + 1;
   }

   else
   {
    PackCount = (int)(FileLength / PackSize);
   }

   Console.WriteLine("Start Recieve");
   var tasks = new Task[PackCount]; //多线程任务

   for (int index = 0; index < PackCount; index++)
   {

    int Threadindex = index; //这步很关键,在Task()里的绝对不能直接使用index
    var task = new Task(() =>
    {
     string tempfilepath = @"E:\Test\TestFile\Temp\" + "QS_" + Threadindex + "_" + PackCount; //临时文件路径

     using (FileStream tempstream = new FileStream(tempfilepath, FileMode.Create, FileAccess.Write, FileShare.Write))
     {
      int length = (int)Math.Min(PackSize, FileLength - Threadindex * PackSize);

      var bytes = GetFile(Threadindex*PackCount, length);

      tempstream.Write(bytes, 0, length);
      tempstream.Flush();
      tempstream.Close();
      tempstream.Dispose();
     }
    });
    tasks[Threadindex] = task;
    task.Start();
   }

   Task.WaitAll(tasks); //等待所有线程完成
   Console.WriteLine("Recieve End");

   //检测有哪些数据包未下载
   Console.WriteLine("Start Compare");
   DirectoryInfo TempDir = new DirectoryInfo(@"E:\Test\TestFile\temp"); //临时文件夹路径
   List<string> Comparefiles = new List<string>();

   for (int i = 0; i < PackCount; i++)
   {
    bool hasfile = false;
    foreach (FileInfo Tempfile in TempDir.GetFiles())
    {
     if (Tempfile.Name.Split('_')[1] == i.ToString())
     {
      hasfile = true;
      break;
     }
    }
    if (hasfile == false)
    {
     Comparefiles.Add(i.ToString());
    }
   }

   //最后补上这些缺失的文件
   if (Comparefiles.Count > 0)
   {
    foreach (string com_index in Comparefiles)
    {
     string tempfilepath = @"E:\Test\TestFile\Temp\" + "QS_" + com_index+ "_" + PackCount;
     using (FileStream Compstream = new FileStream(tempfilepath, FileMode.Create, FileAccess.Write, FileShare.Write))
     {
      int length = (int)Math.Min(PackSize, FileLength - Convert.ToInt32(com_index) * PackSize);
      var bytes = GetFile(Convert.ToInt32(com_index)*PackCount, length);
      Compstream.Write(bytes, 0, length);
      Compstream.Flush();
      Compstream.Close();
      Compstream.Dispose();
     }
    }

   }
   Console.WriteLine("Compare End");

   //准备将临时文件融合并写到1.msi中
   Console.WriteLine("Start Write");
   using (FileStream writestream = new FileStream(LocalSavePath, FileMode.Create, FileAccess.Write, FileShare.Write))
   {
    foreach (FileInfo Tempfile in TempDir.GetFiles())
    {
     using (FileStream readTempStream = new FileStream(Tempfile.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
     {
      long onefileLength = Tempfile.Length;
      byte[] buffer = new byte[Convert.ToInt32(onefileLength)];
      readTempStream.Read(buffer, 0, Convert.ToInt32(onefileLength));
      writestream.Write(buffer, 0, Convert.ToInt32(onefileLength));
     }
    }
    writestream.Flush();
    writestream.Close();
    writestream.Dispose();
   }
   Console.WriteLine("Write End");

   //删除临时文件
   Console.WriteLine("Start Delete Temp Files");
   foreach (FileInfo Tempfile in TempDir.GetFiles())
   {
    Tempfile.Delete();
   }
   Console.WriteLine("Delete Success");
   Console.ReadKey();
  }

  //这个方法可以放到Remoting或者WCF服务中去,然后本地调用该方法即可实现多线程断点续传
  public static byte[] GetFile(int start, int length)
  {
   string SeverFilePath = @"E:\Test\TestFile\Server\1.msi";
   using (FileStream ServerStream = new FileStream(SeverFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 1024*80, true))
   {
    byte[] buffer = new byte[length];
    ServerStream.Position = start;
    //ServerStream.Seek(start, SeekOrigin.Begin);
    ServerStream.Read(buffer, 0, length);
    return buffer;
   }
  }
 }
}

二、讨论

1)需要注意的是第44行,不能直接使用index变量在Task()里进行操作,而是要将它赋给Threadindex,让Threadindex在Task()里,不然会直接报错,为什么呢?查看链接

2)70至108行代码可以在外面再套一层while循环,循环检测临时文件是否下完整了,然后再定义一个检测最大上限,超过这个上限就放弃本次更新,当用户的网络恢复正常后下次再做更新操作。所以说放临时文件的文件夹最好要包含版本信息,不会把2.0.0的临时文件和1.0.0的临时文件搞混。

3) FileStream.Position 与 FileStream.Seek(long offset, SeekOrigin seekorigin) 的作用都是获取流的指针位置,当文件路径使用绝对路径时使用Position;相对路径时使用Seek方法,查看链接

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • C#多线程爬虫抓取免费代理IP的示例代码

    这里用到一个HTML解析辅助类:HtmlAgilityPack,如果没有网上找一个增加到库里,这个插件有很多版本,如果你开发环境是使用VS2005就2.0的类库,VS2010就使用4.0,以此类推..........然后直接创建一个控制台应用,将我下面的代码COPY替换就可以运行,下面就来讲讲我两年前做爬虫经历,当时是给一家公司做,也是用的C#,不过当时遇到一个头痛的问题就是抓的图片有病毒,然后系统挂了几次.所以抓网站图片要注意安全,虽然我这里没涉及到图片,但是还是提醒下看文章的朋友. clas

  • C# 文件上传下载(Excel导入,多线程下载)功能的实现代码

    废话不多说了,直接给大家贴代码,具体代码如下所示: //打开Excel文件,转换为DataTable DataTable dtExcel; private void OpenFile() { OpenFileDialog dialog = new OpenFileDialog(); dialog.Filter = "Microsoft Excel files(*.xls)|*.xls;*.xlsx"; //筛选打开文件类型 :图片 *.jpg|*.jpg|*.bmp|*.bmp ;&q

  • 详解C#多线程之线程同步

    多线程内容大致分两部分,其一是异步操作,可通过专用,线程池,Task,Parallel,PLINQ等,而这里又涉及工作线程与IO线程:其二是线程同步问题,鄙人现在学习与探究的是线程同步问题. 通过学习<CLR via C#>里面的内容,对线程同步形成了脉络较清晰的体系结构,在多线程中实现线程同步的是线程同步构造,这个构造分两大类,一个是基元构造,一个是混合构造.所谓基元则是在代码中使用最简单的构造.基原构造又分成两类,一个是用户模式,另一个是内核模式.而混合构造则是在内部会使用基元构造的用户模

  • C#中的多线程超时处理实践方案

    最近我正在处理C#中关于timeout行为的一些bug.解决方案非常有意思,所以我在这里分享给广大博友们. 我要处理的是下面这些情况: 我们做了一个应用程序,程序中有这么一个模块,它的功能向用户显示一个消息对话框,15秒后再自动关闭该对话框.但是,如果用户手动关闭对话框,则在timeout时我们无需做任何处理. 程序中有一个漫长的执行操作.如果该操作持续5秒钟以上,那么请终止这个操作. 我们的的应用程序中有执行时间未知的操作.当执行时间过长时,我们需要显示一个"进行中"弹出窗口来提示用

  • C#使用后台线程BackgroundWorker处理任务的总结

    在一些耗时的操作过程中,在长时间运行时可能会导致用户界面 (UI) 处于停止响应状态,用户在这操作期间无法进行其他的操作,为了不使UI层处于停止响应状态,我们倾向推荐用户使用BackgroundWorker来进行处理,这个后台的线程处理,可以很好的实现常规操作的同时,还可以及时通知UI,包括当前处理信息和进度等,这个BackgroundWorker的处理在百度里面也是有很多使用的介绍,本篇随笔主要是做一些自己的使用总结,希望也能给读者提供一个参考. 在使用BackgroundWorker的过程中

  • C#多线程之Thread类详解

    使用System.Threading.Thread类可以创建和控制线程. 常用的构造函数有: // 摘要: // 初始化 System.Threading.Thread 类的新实例,指定允许对象在线程启动时传递给线程的委托. // // 参数: // start: // System.Threading.ParameterizedThreadStart 委托,它表示此线程开始执行时要调用的方法. // // 异常: // System.ArgumentNullException: // star

  • C#线程倒计时器源码分享

    本文实例为大家分享了C#线程倒计时器源码,供大家参考,具体内容如下 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Windows.Forms; namespace ListZZBG { class TimeHeleper { Thread thread; private TimeSpan time;

  • C#多线程之Semaphore用法详解

    Semaphore:可理解为允许线程执行信号的池子,池子中放入多少个信号就允许多少线程同时执行. private static void MultiThreadSynergicWithSemaphore() { //0表示创建Semaphore时,拥有可用信号量数值 //1表示Semaphore中,最多容纳信号量数值 Semaphore semaphore = new Semaphore(0, 1); Thread thread1 = new Thread(() => { //线程首先WaitO

  • C# 线程同步详解

    前言 当线程池的线程阻塞时,线程池会创建额外的线程,而创建.销毁和调度线程所需要相当昂贵的内存资源,另外,很多的开发人员看见自己程序的线程没有做任何有用的事情时习惯创建更多的线程,为了构建可伸缩.响应灵敏的程序,我们在前面介绍了C#异步编程详解 但是异步编程同样也存在着很严重的问题,如果两个不同的线程访问相同的变量和数据,按照我们异步函数的实现方式,不可能存在两个线程同时访问相同的数据,这个时候我们就需要线程同步.多个线程同时访问共享数据的时,线程同步能防止数据损坏,之所以强调同时这个概念,因为

  • C#开启线程的四种方式示例详解

    一.异步委托开启线程 public static void Main(string[] args){ Action<int,int> a=add; a.BeginInvoke(3,4,null,null);//前两个是add方法的参数,后两个可以为空 Console.WriteLine("main()"); Console.ReadKey(); } static void add(int a,int b){ Console.WriteLine(a+b); } 运行结果: 如

  • C#多线程中如何运用互斥锁Mutex

    互斥锁(Mutex) 互斥锁是一个互斥的同步对象,意味着同一时间有且仅有一个线程可以获取它. 互斥锁可适用于一个共享资源每次只能被一个线程访问的情况 函数: //创建一个处于未获取状态的互斥锁 Public Mutex(): //如果owned为true,互斥锁的初始状态就是被主线程所获取,否则处于未获取状态 Public Mutex(bool owned): 如果要获取一个互斥锁.应调用互斥锁上的WaitOne()方法,该方法继承于Thread.WaitHandle类 它处于等到状态直至所调用

  • c#中Winform实现多线程异步更新UI(进度及状态信息)

    引言 在进行Winform程序开发需要进行大量的数据的读写操作的时候,往往会需要一定的时间,然在这个时间段里面,界面ui得不到更新,导致在用户看来界面处于假死的状态,造成了不好的用户体验.所以在大量数据操作的应用上,需要使用多线程来处理这种情况.在c#中使用多线程很方便只需要使用System.Threading.Thread的一个实例的Start方法就行了,但是如何实现多线程之间的交互就不是那么简单.本文实现了用子线程去处理数据,并实时更新主线程的ui状态了.下面就开始一步步的去实现异步线程更新

  • C#多线程ThreadPool线程池详解

    简单说明一下: 线程池可以看做容纳线程的容器:一个应用程序最多只能有一个线程池:ThreadPool静态类通过QueueUserWorkItem()方法将工作函数排入线程池: 每排入一个工作函数,就相当于请求创建一个线程: 线程池的作用: 1.线程池是为突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少了创建和销毁线程所需的时间,从而提高效率. 2.如果一个线程的时间非常长,就没必要用线程池了(不是不能作长时间操作,而是不宜.),况且我们还不能控制线程池中线程的开始.挂起.和

  • C#多线程之线程控制详解

    本文为大家分享了C#多线程之线程控制,供大家参考,具体内容如下 方案一: 调用线程控制方法.启动:Thread.Start();停止:Thread.Abort();暂停:Thread.Suspend();继续:Thread.Resume(); private void btn_Start_Click(object sender, EventArgs e) { mThread.Start(); // 开始 } private void btn_Stop_Click(object sender, E

随机推荐