Cool_Breeze 发表于 2021-5-6 18:55

C# 异步读取文件任务,一次取消不了任务,需要多次才能取消任务

本帖最后由 Cool_Breeze 于 2021-5-24 11:30 编辑

请务必将任务理解为工作的异步抽象,而非 在线程之上的抽象。 默认情况下,任务在当前线程上执行,且在适当时会将工作委托给操作系统。

解决了。异步执行时,主线程会收到终止异常。所以需要在主线程中终止任务后,在开始新的任务!using System;
using System.Drawing;
using System.Threading;
using System.IO;
using System.Threading.Tasks;
using System.Windows.Forms;

public class Form1 : Form
{
   
    public static void Main()
    {
      Application.EnableVisualStyles();
      Application.Run(new Form1());
    }

    private readonly ProgressBar pbar;
    private readonly Label label;
    private readonly Button StartPausebut;
    private readonly Button openFileBut;
    private readonly Button Stopbut;
    private readonly Button ShowHandle;
    private readonly ListBox fileList;
    private static CancellationTokenSource source = new CancellationTokenSource();
    private readonly EventWaitHandle StartPausebutEventsynchro = new EventWaitHandle(false, EventResetMode.ManualReset);
    private string selectFileName;
    private string oldSelectFileName;
    private Task task;

    // 窗体
    public Form1()
    {
      // 进度条
      pbar = new ProgressBar();
      pbar.Width = 500;
      pbar.Location = new Point(10, 20);
      pbar.Visible = true;

      // 开始,暂停按键
      StartPausebut = new Button();
      StartPausebut.Text = "开始";
      StartPausebut.Location = new Point(100, 60);
      StartPausebut.Click += new EventHandler(StartPausebut_Click);
      
      // 上一个
      Button selectUP = new Button();
      selectUP.Text = "上一个";
      selectUP.Location = new Point(10, 90);
      selectUP.Click += new EventHandler(selectUP_Click);

      // 下一个
      Button selectDown = new Button();
      selectDown.Text = "下一个";
      selectDown.Location = new Point(100, 90);
      selectDown.Click += new EventHandler(selectDown_Click);

      // 取消按键
      Stopbut = new Button();
      Stopbut.Text = "终止读取";
      Stopbut.Location = new Point(190, 60);
      Stopbut.Click += new EventHandler(Stopbut_Click);

      // 显示窗口句柄
      ShowHandle = new Button();
      ShowHandle.Text = "显示窗口句柄";
      ShowHandle.Location = new Point(280, 60);
      ShowHandle.Click += ShowHandle_Click;

      // 选择文件状态
      openFileBut = new Button();
      openFileBut.Text = "选择文件";
      openFileBut.Location = new Point(10, 60);
      openFileBut.Click += new EventHandler(openFileBut_Click);
      
      // 加载文件列表
      Button loadBut = new Button();
      loadBut.Text = "加载文件列表";
      loadBut.Width = 100;
      loadBut.Location = new Point(370, 60);
      loadBut.Click += new EventHandler(loadBut_Click);

      // 提示信息标签
      label = new Label();
      label.Location = new Point(10, 120);
      label.AutoSize = true;
      
      //文件列表内容
      fileList = new ListBox();
      fileList.Size = new System.Drawing.Size(500,400);
      fileList.Location = new Point(10, 140);
      fileList.MultiColumn = true;

      this.Controls.Add(StartPausebut);
      this.Controls.Add(pbar);
      this.Controls.Add(label);
      this.Controls.Add(Stopbut);
      this.Controls.Add(openFileBut);
      this.Controls.Add(ShowHandle);
      this.Controls.Add(fileList);
      this.Controls.Add(loadBut);
      this.Controls.Add(selectUP);
      this.Controls.Add(selectDown);
      this.Width = 600;
      this.Height = 600;
    }

    // 开始,暂停 按键事件
    private void StartPausebut_Click(object sender, EventArgs e)
    {
      Application.DoEvents();

      if (StartPausebut.Text == "开始")
      {
            StartPausebut.Text = "暂停";
            StartPausebutEventsynchro.Set(); // 任务非阻塞状态
            if (task == null || task.IsCompleted)
            {
                oldSelectFileName = selectFileName;
                task = Task.Run(() => openFileStream());               
            }
            else if (selectFileName != oldSelectFileName)
            {
                Stopbut_Click(this, new EventArgs());
                task = Task.Run(() => openFileStream());
                StartPausebut.Text = "暂停";
            }
            return ;
      }
      else if (StartPausebut.Text == "暂停")
      {
            StartPausebut.Text = "开始";
            StartPausebutEventsynchro.Reset(); // 任务阻塞状态
            return ;
      }
    }

    // 选择文件
    private void openFileBut_Click(object sender, EventArgs e)
    {
      // 选择文件对话框
      OpenFileDialog dlg = new OpenFileDialog();
      dlg.Filter = "Text documents (*.*)|*.*";
      dlg.ShowDialog();
      selectFileName = dlg.FileName;
      label.Text = selectFileName;
      if (selectFileName == "")
      {
            return;
      }
      
    }

    // 取消任务
    private void Stopbut_Click(object sender, EventArgs e)
    {
      // if (!source.Token.IsCancellationRequested)
      try
      {
            if (task.Status != TaskStatus.RanToCompletion)
            {
                source.Cancel(); // 取消任务
                StartPausebutEventsynchro.Reset();
                Application.DoEvents();
                StartPausebutEventsynchro.Set();            
            }
            else
            {
                return;
            }            
      }
      catch {return;}
      try
      {
            task.Wait();
      }
      catch
      {
            StartPausebut.Text = "开始";
            source.Dispose();
            source = new CancellationTokenSource(); //重新获取取消任务对象
      }
    }

    // 显示窗口句柄
    private void ShowHandle_Click(object sender, EventArgs e)
    {
      label.Text = Handle.ToString("X");
    }

    // 文件内容读取(每次读取 4MB 内容)
    private async Task openFileStream()
    {
      pbar.Value = 0;
      // byte[] content = new byte;
      byte[] content = new byte;
      int Count = 0;
      using (FileStream fr = new FileStream(selectFileName, FileMode.Open))
      {
            pbar.Maximum = (int)fr.Length; // 文件过大这里会内存溢出
            int total = 0;
            while (true)
            {
                // 阻塞线程事件
                // await Task.Run(() => StartPausebutEventsynchro.WaitOne());
                StartPausebutEventsynchro.WaitOne();
               
                source.Token.ThrowIfCancellationRequested();
                // 异步读取 100 字节 文件内容
                total = await fr.ReadAsync(content, 0, content.Length, source.Token);

                if (total == 0)
                {
                  break;
                }

                // 进度条
                Count += total;
                label.Text = Count.ToString() + " 字节";
                pbar.Step = total;
                pbar.PerformStep();
                Application.DoEvents();
            }
      }
      StartPausebut.Text = "开始";
    }
   
    // 加载文件事件
    private void loadBut_Click(object sender, EventArgs e)
    {
      if (selectFileName == "" || selectFileName == null) return;
      fileList.BeginUpdate();
      fileList.Items.Clear();
      string filePath = Path.GetDirectoryName(selectFileName);
      foreach (var n in Directory.GetFiles(filePath))
      {
            fileList.Items.Add(n);
      }
      fileList.EndUpdate();
    }

    // 文件列表选中条上移
    private void selectUP_Click(object sender, EventArgs e)
    {
      if (fileList.Items.Count == 0) return;
      int count = fileList.Items.Count;
      int current = fileList.SelectedIndex;
      current--;
      if (current < 0)
      {
            current = count - 1;
      }
      fileList.SelectedIndex = current;
      label.Text = fileList.SelectedItem.ToString();
      selectFileName = label.Text;
    }
   
    // 文件列表选中条下移
    private void selectDown_Click(object sender, EventArgs e)
    {
      if (fileList.Items.Count == 0) return;
      int count = fileList.Items.Count;
      int current = fileList.SelectedIndex;
      current++;
      if (current >= count)
      {
            current = 0;
      }
      fileList.SelectedIndex = current;
      label.Text = fileList.SelectedItem.ToString();
      selectFileName = label.Text;
    }}

wwh1004 发表于 2021-5-6 20:41

``` cs
using System;
using System.Drawing;
using System.Threading;
using System.IO;
using System.Threading.Tasks;
using System.Windows.Forms;

public class Form1 : Form {
       
        public static void Main() {
                Application.EnableVisualStyles();
                Application.Run(new Form1());
        }

        private ProgressBar pbar;
        private Label label;
        private Button StartPausebut;
        private Button openFileBut;
        private static CancellationTokenSource source = new CancellationTokenSource();
        private bool paused;
        private bool stopped;
        private System.Threading.SemaphoreSlim StartPausebutEventsynchro = new SemaphoreSlim(0);
        private string selectFileName;
        private bool targetEnd = false;

        // 窗体
        public Form1() {
                // 进度条
                pbar = new ProgressBar();
                pbar.Width = 500;
                pbar.Location = new Point(10, 20);
                pbar.Visible = true;

                // 开始,暂停按键
                StartPausebut = new Button();
                StartPausebut.Text = "开始";
                StartPausebut.Location = new Point(100, 60);
                StartPausebut.Click += new EventHandler(StartPausebut_Click);

                // 选择文件状态
                openFileBut = new Button();
                openFileBut.Text = "选择文件";
                openFileBut.Location = new Point(10, 60);
                openFileBut.Click += new EventHandler(openFileBut_Click);

                // 提示信息标签
                label = new Label();
                label.Location = new Point(10, 90);
                label.AutoSize = true;

                this.Controls.Add(StartPausebut);
                this.Controls.Add(pbar);
                this.Controls.Add(label);
                this.Controls.Add(openFileBut);
                this.Width = 600;
        }


        // 开始,暂停 按键事件
        private void StartPausebut_Click(object sender, EventArgs e) {
                if (StartPausebut.Text == "开始") {
                        StartPausebut.Text = "暂停";
                        paused = true; // 任务非阻塞状态
                }
                else if (StartPausebut.Text == "暂停") {
                        StartPausebut.Text = "开始";
                        paused = false;
                        StartPausebutEventsynchro.Release(); // 任务阻塞状态
                }
        }

        // 选择文件
        private async void openFileBut_Click(object sender, EventArgs e) {
                // 选择文件对话框
                OpenFileDialog dlg = new OpenFileDialog();
                dlg.Filter = "Text documents (*.*)|*.*";
                dlg.ShowDialog();
                selectFileName = dlg.FileName;
                label.Text = selectFileName;
                if (selectFileName == "") {
                        return;
                }

                try {
                        // 每次选择文件都会取消之前的任务!
                        // 取消任务
                        if (targetEnd) {
                                source.Cancel(); // 取消任务
                                await Task.Run(() => SpinWait.SpinUntil(() => stopped));
                                targetEnd = false;
                        }
                        await openFileStream(); // 捕获取消任务后引发的异常
                }
                catch (Exception err) {
                        label.Text = err.Message;
                }
                finally {
                        source.Dispose(); // 释放资源
                        source = new CancellationTokenSource(); //重新获取取消任务对象
                        stopped = true;
                }
        }

        // 文件内容读取(每次读取 100 字节 内容)
        private async Task openFileStream() {
                targetEnd = true; // 标记读取文件状态(正在读取,完成读取)
                pbar.Value = 0;
                byte[] content = new byte;
                int Count = 0;
                using (FileStream fr = new FileStream(selectFileName, FileMode.Open, FileAccess.Read, FileShare.Read)) {
                        pbar.Maximum = (int)fr.Length;
                        int total = 0;
                        while (true) {
                                // 阻塞线程事件
                                if (paused)
                                        await StartPausebutEventsynchro.WaitAsync(source.Token);

                                // 异步读取 100 字节 文件内容
                                total = await fr.ReadAsync(content, 0, content.Length, source.Token);
                                if (total == 0) {
                                        break;
                                }

                                // 进度条
                                Count += total;
                                label.Text = Count.ToString() + " 字节";
                                pbar.Step = total;
                                pbar.PerformStep();
                        }
                }
                targetEnd = false;
        }
}
```

wwh1004 发表于 2021-5-6 20:02

说实话,代码问题挺多的,全修改完挺麻烦的,目前就把看见的逻辑错误改正确了。

``` cs
using System;
using System.Drawing;
using System.Threading;
using System.IO;
using System.Threading.Tasks;
using System.Windows.Forms;

public class Form1 : Form {
       
        public static void Main() {
                Application.EnableVisualStyles();
                Application.Run(new Form1());
        }

        private ProgressBar pbar;
        private Label label;
        private Button StartPausebut;
        private Button openFileBut;
        private static CancellationTokenSource source = new CancellationTokenSource();
        private bool paused;
        private System.Threading.SemaphoreSlim StartPausebutEventsynchro = new SemaphoreSlim(0);
        private string selectFileName;
        private bool targetEnd = false;

        // 窗体
        public Form1() {
                // 进度条
                pbar = new ProgressBar();
                pbar.Width = 500;
                pbar.Location = new Point(10, 20);
                pbar.Visible = true;

                // 开始,暂停按键
                StartPausebut = new Button();
                StartPausebut.Text = "开始";
                StartPausebut.Location = new Point(100, 60);
                StartPausebut.Click += new EventHandler(StartPausebut_Click);

                // 选择文件状态
                openFileBut = new Button();
                openFileBut.Text = "选择文件";
                openFileBut.Location = new Point(10, 60);
                openFileBut.Click += new EventHandler(openFileBut_Click);

                // 提示信息标签
                label = new Label();
                label.Location = new Point(10, 90);
                label.AutoSize = true;

                this.Controls.Add(StartPausebut);
                this.Controls.Add(pbar);
                this.Controls.Add(label);
                this.Controls.Add(openFileBut);
                this.Width = 600;
        }


        // 开始,暂停 按键事件
        private void StartPausebut_Click(object sender, EventArgs e) {
                if (StartPausebut.Text == "开始") {
                        StartPausebut.Text = "暂停";
                        paused = true; // 任务非阻塞状态
                }
                else if (StartPausebut.Text == "暂停") {
                        StartPausebut.Text = "开始";
                        paused = false;
                        StartPausebutEventsynchro.Release(); // 任务阻塞状态
                }
        }

        // 选择文件
        private async void openFileBut_Click(object sender, EventArgs e) {
                // 选择文件对话框
                OpenFileDialog dlg = new OpenFileDialog();
                dlg.Filter = "Text documents (*.*)|*.*";
                dlg.ShowDialog();
                selectFileName = dlg.FileName;
                label.Text = selectFileName;
                if (selectFileName == "") {
                        return;
                }

                try {
                        // 每次选择文件都会取消之前的任务!
                        // 取消任务
                        if (targetEnd) {
                                source.Cancel(); // 取消任务
                                targetEnd = false;
                        }
                        await openFileStream(); // 捕获取消任务后引发的异常
                }
                catch (Exception err) {
                        label.Text = err.Message;
                }
                finally {
                        source.Dispose(); // 释放资源
                        source = new CancellationTokenSource(); //重新获取取消任务对象
                }
        }


        // 文件内容读取(每次读取 100 字节 内容)
        private async Task openFileStream() {
                targetEnd = true; // 标记读取文件状态(正在读取,完成读取)
                pbar.Value = 0;
                byte[] content = new byte;
                int Count = 0;
                using (FileStream fr = new FileStream(selectFileName, FileMode.Open)) {
                        pbar.Maximum = (int)fr.Length;
                        int total = 0;
                        while (true) {
                                // 阻塞线程事件
                                if (paused)
                                        await StartPausebutEventsynchro.WaitAsync();

                                // 异步读取 100 字节 文件内容
                                total = await fr.ReadAsync(content, 0, content.Length, source.Token);
                                if (total == 0) {
                                        break;
                                }

                                // 进度条
                                Count += total;
                                label.Text = Count.ToString() + " 字节";
                                pbar.Step = total;
                                pbar.PerformStep();
                                Application.DoEvents();
                        }
                }
                targetEnd = false;
        }
}
```

wwh1004 发表于 2021-5-6 20:06

本帖最后由 wwh1004 于 2021-5-6 20:09 编辑

那个targetEnd相关的,没太看懂是干嘛的....如果任务已经完成了,那不需要手动取消的,可以不需要传那个cancellationtoken,也不需要targetEnd

再补充个下,那个分块读取的缓冲区设置的太小了,vs调试器看着clr在疯狂进行gc,按道理给个几mb都没问题。如果只是要文件完整内容,一次性全部读出来也可以,.net内部已经设计好缓冲区了,不需要自己再弄个。

Cool_Breeze 发表于 2021-5-6 20:08

wwh1004 发表于 2021-5-6 20:02
说实话,代码问题挺多的,全修改完挺麻烦的,目前就把看见的逻辑错误改正确了。

``` cs


感谢大佬指点,但是问题还是存在,打开大文件,中途还是无法取消!

wwh1004 发表于 2021-5-6 20:10

Cool_Breeze 发表于 2021-5-6 20:08
感谢大佬指点,但是问题还是存在,打开大文件,中途还是无法取消!

取消就是从头开始读取了,你是要暂停还是取消?

Cool_Breeze 发表于 2021-5-6 20:13

wwh1004 发表于 2021-5-6 20:10
取消就是从头开始读取了,你是要暂停还是取消?

就是打开大文件后,还在读取中,又重新选择一个小文件打开,小文件跑完,大文件还在跑。逻辑是:打开小文件时,读取大文件任务就该是取消了!

Cool_Breeze 发表于 2021-5-6 20:16

wwh1004 发表于 2021-5-6 20:06
那个targetEnd相关的,没太看懂是干嘛的....如果任务已经完成了,那不需要手动取消的,可以不需要传那个can ...

看到 有个 ReadAsync 重载是没有 token的。targetEnd其实就是检测读取文件任务是否完成!

Cool_Breeze 发表于 2021-5-6 20:17

wwh1004 发表于 2021-5-6 20:06
那个targetEnd相关的,没太看懂是干嘛的....如果任务已经完成了,那不需要手动取消的,可以不需要传那个can ...

感谢指正! 我就说读取个文件占用cpu那么多资源!

Cool_Breeze 发表于 2021-5-6 20:46

wwh1004 发表于 2021-5-6 20:41
``` cs
using System;
using System.Drawing;


成功了。大佬能不能教教 () => stopped 这个委托它做了啥?
页: [1] 2
查看完整版本: C# 异步读取文件任务,一次取消不了任务,需要多次才能取消任务