Асинхронное копирование в купе с progressBar'om
На самом деле задача на половину выполнена, я имею готовое решение не без помощи backgoundWorker:
Функция копирования:
private void copy_file(string source_, string des)
{
FileStream fsOut = new FileStream(des, FileMode.Create);
FileStream fsIn = new FileStream(source_, FileMode.Open);
byte[] bt = new byte[1048756];
int readByte;
while ((readByte = fsIn.Read(bt, 0, bt.Length)) > 0)
{
fsOut.Write(bt, 0, readByte);
backgroundWorker1.ReportProgress((int)(fsIn.Position * 100 / fsIn.Length));
}
fsIn.Close();
fsOut.Close();
}
DoWork:
private void Worker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
copy_file(server_path + filename, Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\" + filename);
}
RunWorkerCompleted:
private void Worker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
if (e.Error == null)
{
err_msg.Text = "Файл находится на рабочем столе!";
}
else
{
err_msg.Text = "Произошла ошибка.";
}
download_sound.Enabled = true;
}
ProgressChanged:
private void Worker_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
err_msg.Text = progressBar1.Value.ToString() + "%";
}
Click Button:
Worker.RunWorkerAsync();
Выше указанный метод отлично справляется, но при использовании этого метода в Асинхронных функциях(например private async void button_click()), я получаю эффект очередности потоков, эм... файл копируется - помещается в нужную папку - прогресс бар мгновенно заполняется. Пробовал копировать через сеть, та же беда. Но к счастью работает когда async у btn отсутствует.
Теперь к сути:
Т.к. изначально мой план был использовать async\await методы, я перешел к такому виду копирования файлов:
public async Task CopyFilesAsync(StreamReader Source, StreamWriter Destination)
{
char[] buffer = new char[0x1000];
int numRead;
while ((numRead = await Source.ReadAsync(buffer, 0, buffer.Length)) != 0)
{
await Destination.WriteAsync(buffer, 0, numRead);
}
}
_
public async Task CopyFile(string startDirectoy, string endDirectory)
{
using (StreamReader SourceReader = File.OpenText(startDirectoy))
{
using (StreamWriter DestinationWriter = File.CreateText(endDirectory))
{
await CopyFilesAsync(SourceReader, DestinationWriter);
SourceReader.Close();
DestinationWriter.Close();
}
}
}
Никак не могу сообразить каким образом мне заставить progressBar выполнять мою задачу. Я так же не могу достучаться до StreamReader.Position использовать:
progressBar1.Value=(int)((StreamReader.BaseStream.Position/StreamReader.BaseStream.Length)*100);
у меня не вышло. ProgressBar работал у меня отдельно от всего >< Если есть пару советов, прошу их дать, буду очень благодарен!
Ответы (1 шт):
Был у меня пример скачивания файла, почему бы не переписать его под копирование
- Копирует файл асинхронно
- По нажатию кнопки Cancel можно остановить копирование
button1- кнопка Copy,button2- кнопка Cancel,progressBar1- прогресс бар- реализован отчет о прогрессе с помощью интерфейса IProgress и класса Progress
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
button2.Enabled = false;
}
// Токен отмены служит для прерывания работы копирования в любой момент
private CancellationTokenSource _cts;
private async Task CopyFileAsync(string srcPath, string dstPath, IProgress<int> status, CancellationToken token)
{
const int bufferLength = 16384;
using FileStream src = File.OpenRead(srcPath);
using FileStream dst = File.Create(dstPath);
long currentPosition = 0;
long contentLength = src.Length;
int progress = -1;
int oldProgress;
byte[] buffer = new byte[bufferLength];
int bytesReceived;
while ((bytesReceived = await src.ReadAsync(buffer, 0, bufferLength, token).ConfigureAwait(false)) > 0)
{
await dst.WriteAsync(buffer, 0, bytesReceived, token).ConfigureAwait(false);
currentPosition += bytesReceived;
oldProgress = progress;
progress = (int)(currentPosition * 100 / contentLength);
// так как значение от 0 до 100, нет особого смысла повтороно обновлять интерфейс, если значение не изменилось.
if (oldProgress != progress)
{
status?.Report(progress);
}
}
}
private async void button1_Click(object sender, EventArgs e)
{
if (_cts != null)
return;
button1.Enabled = false;
button2.Enabled = true;
using (_cts = new CancellationTokenSource())
{
try
{
await CopyFileAsync("file1.txt", "file2.txt", new Progress<int>(v => { progressBar1.Value = v; }), _cts.Token);
}
catch (OperationCanceledException) { } // была отмена, ничего не делать
catch (Exception ex)
{
MessageBox.Show(ex.ToString(), ex.GetType().Name);
}
progressBar1.Value = 0;
}
_cts = null;
button1.Enabled = true;
button2.Enabled = false;
}
private void button2_Click(object sender, EventArgs e)
{
_cts?.Cancel();
}
}
Чтобы прогресс обновлялся с учетом медленной анимации прогресс-бара винформ
private async Task CopyFileAsync(string srcPath, string dstPath, IProgress<int> status, CancellationToken token)
{
const int bufferLength = 16384;
using FileStream src = File.OpenRead(srcPath);
using FileStream dst = File.Create(dstPath);
long currentPosition = 0;
long contentLength = src.Length;
int progress = -1;
int oldProgress;
byte[] buffer = new byte[bufferLength];
int bytesReceived;
long elapsed = 0;
var sw = Stopwatch.StartNew();
while ((bytesReceived = await src.ReadAsync(buffer, 0, bufferLength, token).ConfigureAwait(false)) > 0)
{
await dst.WriteAsync(buffer, 0, bytesReceived, token).ConfigureAwait(false);
currentPosition += bytesReceived;
oldProgress = progress;
progress = (int)(currentPosition * 100 / contentLength);
// 1 / 30 = 0.033 - 30 кадров в секунду
if (oldProgress != progress && (progress == 100 || sw.ElapsedMilliseconds - 33 > elapsed))
{
status?.Report(progress);
elapsed = sw.ElapsedMilliseconds;
}
}
}
Совет от @PavelMayorov стартовать копирование сразу в пуле потоков можно реализовать вот так
IProgress<int> status = new Progress<int>(v => { progressBar1.Value = v; });
await Task.Run(() => CopyFileAsync("file1.txt", "file2.txt", status, _cts.Token));
