C# Background Worker with zipfile(Ionic.dll) - c#

So i have been trying over and over with lots of trial and error and i cannot seem to get this to work, basically i want to unzip a zip file using the Ionic.DLL from http://dotnetzip.codeplex.com as you can see i have also made a thread about it here: Extract ZipFile Using C# With Progress Report so to basically sum up what i am after.
I have a form with:
3xbuttons: btnCancel, btnBrowse and btnStart,
1xbackground worker: backgroundworker1
1xlabel: label1
1xtextbox: textBox1
1xprogressbar
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Threading;
using System.Text;
using System.Windows.Forms;
using System.IO;
using Ionic.Zip;
namespace BackgroundWorkerSample
{
public partial class Form1 : Form
{
/// <summary>
/// The backgroundworker object on which the time consuming operation shall be executed
/// </summary>
BackgroundWorker m_oWorker;
public Form1()
{
InitializeComponent();
m_oWorker = new BackgroundWorker();
m_oWorker.DoWork += new DoWorkEventHandler(m_oWorker_DoWork);
m_oWorker.ProgressChanged += new ProgressChangedEventHandler(m_oWorker_ProgressChanged);
m_oWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(m_oWorker_RunWorkerCompleted);
m_oWorker.WorkerReportsProgress = true;
m_oWorker.WorkerSupportsCancellation = true;
}
/// <summary>
/// On completed do the appropriate task
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void m_oWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//If it was cancelled midway
if (e.Cancelled)
{
lblStatus.Text = "Task Cancelled.";
}
else if (e.Error != null)
{
lblStatus.Text = "Error while performing background operation.";
}
else
{
lblStatus.Text = "Task Completed...";
}
btnStartAsyncOperation.Enabled = true;
btnCancel.Enabled = false;
}
/// <summary>
/// Notification is performed here to the progress bar
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void m_oWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//Here you play with the main UI thread
progressBar1.Value = e.ProgressPercentage;
lblStatus.Text = "Processing......" + progressBar1.Value.ToString() + "%";
}
/// <summary>
/// Time consuming operations go here </br>
/// i.e. Database operations,Reporting
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void m_oWorker_DoWork(object sender, DoWorkEventArgs e)
{
//NOTE : Never play with the UI thread here...
//time consuming operation
for (int i = 0; i < 100; i++)
{
Thread.Sleep(100);
m_oWorker.ReportProgress(i);
/////////////////////MINEEEEEEEEEEEEEEEEEEEEEEEEE
string INSTALL_LOCATION = "C:" + #"\" + "Program Files (x86)" + #"\" + "TEST_FOLDER" + #"\" + #"\" + "Burgos_Folder";
string DEFAULT_LOCATION = "C:" + #"\" + "Program Files (x86)" + #"\" + "TEST_FOLDER" + #"\" + "test.rar";
if (!Directory.Exists(INSTALL_LOCATION))
{
Directory.CreateDirectory(INSTALL_LOCATION);
}
//if the textbox directory exists
if (Directory.Exists(INSTALL_LOCATION))
{
using (ZipFile zip = ZipFile.Read(DEFAULT_LOCATION))
{
zip.ExtractAll(INSTALL_LOCATION, ExtractExistingFileAction.OverwriteSilently);
}
}
//If cancel button was pressed while the execution is in progress
//Change the state from cancellation ---> cancel'ed
if (m_oWorker.CancellationPending)
{
e.Cancel = true;
m_oWorker.ReportProgress(0);
return;
}
}
//Report 100% completion on operation completed
m_oWorker.ReportProgress(100);
}
private void btnStartAsyncOperation_Click(object sender, EventArgs e)
{
btnStartAsyncOperation.Enabled = false;
btnCancel.Enabled = true;
//Start the async operation here
m_oWorker.RunWorkerAsync();
}
private void btnCancel_Click(object sender, EventArgs e)
{
if (m_oWorker.IsBusy)
{
//Stop/Cancel the async operation here
m_oWorker.CancelAsync();
}
}
}
}
What i would like my winforms to have is simply: browse for a folder, then click Start(btnStart) which starts the zip extraction process which is also shown on the ProgressBar(timewise), and the cancel(btnCancel) button cancels the unzipping processs, i have successfully done everything but i cannot work out how to cancel the unzipping process, it never seems to stop unless i actually close the .exe down. I decided to make a simpler example without the textbox and browse button, and that example is in my previous Asked Question(link above), but i don't see how to implement a way to cancel the unzipping process with the background worker, and i definantly need to use background worker so i can have a progress report on the current situation etc. Can anyone please adapt my examples or provide a example that does this?

I have found a solution, i have moved away from Ionic and added the existing references: System.IO.Compression and System.IO.Compression.FileSystem.

Related

Calling Functions on Unity-Application embedded in Winforms-Application [duplicate]

This question already has answers here:
Send message from one program to another in Unity
(2 answers)
Embed Unity3D app inside WPF application
(1 answer)
Closed 5 years ago.
I am currently developing a simple prototype for an editor. The editor will use WinForms (or WPF, if possible) to provide the main user interface and will also embed a Unity 2017 standalone application to visualise data and provide additional control elements (e.g. zoom, rotate, scroll, ...).
Thanks to this nice post below, getting an embedded Unity application to work within a WinForms-application was shockingly easy.
https://forum.unity.com/threads/unity-3d-within-windows-application-enviroment.236213/
Also, there is a simple example application, which you may access here:
Example.zip
Unfortunately, neither the example, nor any posts I could find answered a very basic question: how do you pass data (or call methods) from your WinForms-application in your embedded Unity-application (and vice versa)?
Is it possible for your WinForms-application to simply call MonoBehaviour-scripts or static methods in your Unity-application? If so, how? If not, what would be a good workaround? And how could the Unity-to-WinForms communication work in return?
Update:
Used the duplicate-pages mentioned by Programmer (link) to implement a solution, which uses named pipes for communication between the WinForms- and Unity-application.
Both applications use BackgroundWorkers, the WinForms-application acts as server (since it is started first and needs an active connection-listener, before the client is started), while the embedded Unity-application acts as client.
Unfortunately, the Unity-application throws a NotImplementedException, stating "ACL is not supported in Mono" when creating the NamedPipeClientStream (tested with Unity 2017.3 and Net 2.0 (not the Net 2.0 subset)). This exception has already been reported in some comments in the post mentioned above, but its unclear, if it has been solved. The proposed solutions of "make sure, that the server is up and running before the client tries to connect" and "start it in admin mode" have been tried, but failed so far.
Solution:
After some more testing, it became clear, that the "ACL is not supported in Mono" exception was caused the TokenImpersonationLevel-parameter used when creating the NamedPipeClientStream-instance. Changing it to TokenImpersonationLevel.None solved the issue.
Here the code used by the WinForms-application, which acts as a named pipe server. Make sure, this script is executed, BEFORE the Unity-application client tries to connect! Also, make sure, that you have build and released the Unity-application before you start the server. Place the Unity executable of your Unity-application in the WinForms-applications folder and name it "Child.exe".
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using System.Diagnostics;
using System.IO.Pipes;
namespace Container
{
public partial class MainForm : Form
{
[DllImport("User32.dll")]
static extern bool MoveWindow(IntPtr handle, int x, int y, int width, int height, bool redraw);
internal delegate int WindowEnumProc(IntPtr hwnd, IntPtr lparam);
[DllImport("user32.dll")]
internal static extern bool EnumChildWindows(IntPtr hwnd, WindowEnumProc func, IntPtr lParam);
[DllImport("user32.dll")]
static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
/// <summary>
/// A Delegate for the Update Log Method.
/// </summary>
/// <param name="text">The Text to log.</param>
private delegate void UpdateLogCallback(string text);
/// <summary>
/// The Unity Application Process.
/// </summary>
private Process process;
/// <summary>
/// The Unity Application Window Handle.
/// </summary>
private IntPtr unityHWND = IntPtr.Zero;
private const int WM_ACTIVATE = 0x0006;
private readonly IntPtr WA_ACTIVE = new IntPtr(1);
private readonly IntPtr WA_INACTIVE = new IntPtr(0);
/// <summary>
/// The Background Worker, which will send and receive Data.
/// </summary>
private BackgroundWorker backgroundWorker;
/// <summary>
/// A Named Pipe Stream, acting as the Server for Communication between this Application and the Unity Application.
/// </summary>
private NamedPipeServerStream namedPipeServerStream;
public MainForm()
{
InitializeComponent();
try
{
//Create Server Instance
namedPipeServerStream = new NamedPipeServerStream("NamedPipeExample", PipeDirection.InOut, 1);
//Start Background Worker
backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.RunWorkerAsync();
//Start embedded Unity Application
process = new Process();
process.StartInfo.FileName = Application.StartupPath + "\\Child.exe";
process.StartInfo.Arguments = "-parentHWND " + splitContainer.Panel1.Handle.ToInt32() + " " + Environment.CommandLine;
process.StartInfo.UseShellExecute = true;
process.StartInfo.CreateNoWindow = true;
process.Start();
process.WaitForInputIdle();
//Embed Unity Application into this Application
EnumChildWindows(splitContainer.Panel1.Handle, WindowEnum, IntPtr.Zero);
//Wait for a Client to connect
namedPipeServerStream.WaitForConnection();
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// Activates the Unity Window.
/// </summary>
private void ActivateUnityWindow()
{
SendMessage(unityHWND, WM_ACTIVATE, WA_ACTIVE, IntPtr.Zero);
}
/// <summary>
/// Deactivates the Unity Window.
/// </summary>
private void DeactivateUnityWindow()
{
SendMessage(unityHWND, WM_ACTIVATE, WA_INACTIVE, IntPtr.Zero);
}
private int WindowEnum(IntPtr hwnd, IntPtr lparam)
{
unityHWND = hwnd;
ActivateUnityWindow();
return 0;
}
private void panel1_Resize(object sender, EventArgs e)
{
MoveWindow(unityHWND, 0, 0, splitContainer.Panel1.Width, splitContainer.Panel1.Height, true);
ActivateUnityWindow();
}
/// <summary>
/// Called, when this Application is closed. Tries to close the Unity Application and the Named Pipe as well.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
try
{
//Close Connection
namedPipeServerStream.Close();
//Kill the Unity Application
process.CloseMainWindow();
Thread.Sleep(1000);
while (process.HasExited == false)
{
process.Kill();
}
}
catch (Exception ex)
{
throw ex;
}
}
private void MainForm_Activated(object sender, EventArgs e)
{
ActivateUnityWindow();
}
private void MainForm_Deactivate(object sender, EventArgs e)
{
DeactivateUnityWindow();
}
/// <summary>
/// A simple Background Worker, which sends Data to the Client via a Named Pipe and receives a Response afterwards.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//Init
UpdateLogCallback updateLog = new UpdateLogCallback(UpdateLog);
string dataFromClient = null;
try
{
//Don't pass until a Connection has been established
while (namedPipeServerStream.IsConnected == false)
{
Thread.Sleep(100);
}
//Created stream for reading and writing
StreamString serverStream = new StreamString(namedPipeServerStream);
//Send to Client and receive Response (pause using Thread.Sleep for demonstration Purposes)
Invoke(updateLog, new object[] { "Send Data to Client: " + serverStream.WriteString("A Message from Server.") + " Bytes." });
Thread.Sleep(1000);
dataFromClient = serverStream.ReadString();
Invoke(updateLog, new object[] { "Received Data from Server: " + dataFromClient });
Thread.Sleep(1000);
Invoke(updateLog, new object[] { "Send Data to Client: " + serverStream.WriteString("A small Message from Server.") + " Bytes." });
Thread.Sleep(1000);
dataFromClient = serverStream.ReadString();
Invoke(updateLog, new object[] { "Received Data from Server: " + dataFromClient });
Thread.Sleep(1000);
Invoke(updateLog, new object[] { "Send Data to Client: " + serverStream.WriteString("Another Message from Server.") + " Bytes." });
Thread.Sleep(1000);
dataFromClient = serverStream.ReadString();
Invoke(updateLog, new object[] { "Received Data from Server: " + dataFromClient });
Thread.Sleep(1000);
Invoke(updateLog, new object[] { "Send Data to Client: " + serverStream.WriteString("The final Message from Server.") + " Bytes." });
Thread.Sleep(1000);
dataFromClient = serverStream.ReadString();
Invoke(updateLog, new object[] { "Received Data from Server: " + dataFromClient });
}
catch(Exception ex)
{
//Handle usual Communication Exceptions here - just logging here for demonstration and debugging Purposes
Invoke(updateLog, new object[] { ex.Message });
}
}
/// <summary>
/// A simple Method, which writes Text into a Console / Log. Will be invoked by the Background Worker, since WinForms are not Thread-safe and will crash, if accessed directly by a non-main-Thread.
/// </summary>
/// <param name="text">The Text to log.</param>
private void UpdateLog(string text)
{
lock (richTextBox_Console)
{
Console.WriteLine(text);
richTextBox_Console.AppendText(Environment.NewLine + text);
}
}
}
}
Attach this code to a GameObject within your Unity-application. Also make sure to reference a GameObject with a TextMeshProUGUI-component (TextMeshPro-Asset, you'll find it in your Asset Store) to the 'textObject'-member, so the application doesn't crash and you can see some debug information.
Also (as stated above) make sure you build and release your Unity-application, name it "Child.exe" and put it in the same folder as your WinForms-application.
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using System;
using System.IO.Pipes;
using System.Security.Principal;
using Assets;
using System.ComponentModel;
using TMPro;
/// <summary>
/// A simple Example Project, which demonstrates Communication between WinForms-Applications and embedded Unity Engine Applications via Named Pipes.
///
/// This Code (Unity) is considered as the Client, which will receive Data from the WinForms-Server and send a Response in Return.
/// </summary>
public class SendAndReceive : MonoBehaviour
{
/// <summary>
/// A GameObject with an attached Text-Component, which serves as a simple Console.
/// </summary>
public GameObject textObject;
/// <summary>
/// The Background Worker, which will send and receive Data.
/// </summary>
private BackgroundWorker backgroundWorker;
/// <summary>
/// A Buffer needed to temporarely save Text, which will be shown in the Console.
/// </summary>
private string textBuffer = "";
/// <summary>
/// Use this for initialization.
/// </summary>
void Start ()
{
//Init the Background Worker to send and receive Data
this.backgroundWorker = new BackgroundWorker();
this.backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
this.backgroundWorker.WorkerReportsProgress = true;
this.backgroundWorker.RunWorkerAsync();
}
/// <summary>
/// Update is called once per frame.
/// </summary>
void Update ()
{
//Update the Console for debugging Purposes
lock (textBuffer)
{
if (string.IsNullOrEmpty(textBuffer) == false)
{
textObject.GetComponent<TextMeshProUGUI>().text = textObject.GetComponent<TextMeshProUGUI>().text + Environment.NewLine + textBuffer;
textBuffer = "";
}
}
}
/// <summary>
/// A simple Background Worker, which receives Data from the Server via a Named Pipe and sends a Response afterwards.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
try
{
//Init
NamedPipeClientStream client = null;
string dataFromServer = null;
//Create Client Instance
client = new NamedPipeClientStream(".", "NamedPipeExample", PipeDirection.InOut, PipeOptions.None, TokenImpersonationLevel.None);
updateTextBuffer("Initialized Client");
//Connect to Server
client.Connect();
updateTextBuffer("Connected to Server");
//Created stream for Reading and Writing
StreamString clientStream = new StreamString(client);
//Read from Server and send Response (flush in between to clear the Buffer and fix some strange Issues I couldn't really explain, sorry)
dataFromServer = clientStream.ReadString();
updateTextBuffer("Received Data from Server: " + dataFromServer);
client.Flush();
updateTextBuffer("Sent Data back to Server: " + clientStream.WriteString("Some data from client.") + " Bytes.");
dataFromServer = clientStream.ReadString();
updateTextBuffer("Received Data from Server: " + dataFromServer);
client.Flush();
updateTextBuffer("Sent Data back to Server: " + clientStream.WriteString("Some more data from client.") + " Bytes.");
dataFromServer = clientStream.ReadString();
updateTextBuffer("Received Data from Server: " + dataFromServer);
client.Flush();
updateTextBuffer("Sent Data back to Server: " + clientStream.WriteString("A lot of more data from client.") + " Bytes.");
dataFromServer = clientStream.ReadString();
updateTextBuffer("Received Data from Server: " + dataFromServer);
client.Flush();
updateTextBuffer("Sent Data back to Server: " + clientStream.WriteString("Clients final message.") + " Bytes.");
//Close client
client.Close();
updateTextBuffer("Done");
}
catch (Exception ex)
{
//Handle usual Communication Exceptions here - just logging here for demonstration and debugging Purposes
updateTextBuffer(ex.Message + Environment.NewLine + ex.StackTrace.ToString() + Environment.NewLine + "Last Message was: " + textBuffer);
}
}
/// <summary>
/// A Buffer, which allows the Background Worker to save Texts, which may be written into a Log or Console by the Update-Loop
/// </summary>
/// <param name="text">The Text to save.</param>
private void updateTextBuffer(string text)
{
lock (textBuffer)
{
if (string.IsNullOrEmpty(textBuffer))
{
textBuffer = text;
}
else
{
textBuffer = textBuffer + Environment.NewLine + text;
}
}
}
}
Also, both scripts need an additional class, which encapsulates the pipe stream, so sending and receiving text becomes much easier.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace Assets
{
/// <summary>
/// Simple Wrapper to write / read Data to / from a Named Pipe Stream.
///
/// Code based on:
/// https://stackoverflow.com/questions/43062782/send-message-from-one-program-to-another-in-unity
/// </summary>
public class StreamString
{
private Stream ioStream;
private UnicodeEncoding streamEncoding;
public StreamString(Stream ioStream)
{
this.ioStream = ioStream;
streamEncoding = new UnicodeEncoding();
}
public string ReadString()
{
int len = 0;
len = ioStream.ReadByte() * 256;
len += ioStream.ReadByte();
byte[] inBuffer = new byte[len];
ioStream.Read(inBuffer, 0, len);
return streamEncoding.GetString(inBuffer);
}
public int WriteString(string outString)
{
byte[] outBuffer = streamEncoding.GetBytes(outString);
int len = outBuffer.Length;
if (len > UInt16.MaxValue)
{
len = (int)UInt16.MaxValue;
}
ioStream.WriteByte((byte)(len / 256));
ioStream.WriteByte((byte)(len & 255));
ioStream.Write(outBuffer, 0, len);
ioStream.Flush();
return outBuffer.Length + 2;
}
}
}
If you read the post down to this point: thank you :) I hope it will help you on your journey to a successfull developer!
Final result of my prototype:
If nothing else, you could fall back on basic file I/O to communicate between the two.

Extract ZipFile Using C# With Progress Report

Can anyone tell me if it is possible(and give an example if it is) on how to have a progress bar(and status label if possible) showing the progress of a ZIP file being extracted using "ZipFile"(Ionic.zip, http://dotnetzip.codeplex.com/)?
My WinForm does a great job at extracting a ZIP file from my chosen path to a new path no worries at all using textboxes and browse buttons and all that jazz... However the only issue is i cannot use anything on my form during this time it is as if it has frozen, but its only because its unzipping the ZIP file in the background.
The ZIP file is a big file and i would like to make it less confusing on what is going on by adding and having a progress bar showing how the unzipping is going with a accurate ETA.
Surely this is possible, i just cannot work out how to do it in C# WinForms, i have had a fairly decent look all around the net without really being able to come across a example i can find suitable for me.
Here is a rough example of what i have:
private void button1_Click(object sender, EventArgs e)
{
var ROBOT0007 = textBox1.Text + #"\" + "ROBOT0007"; //ROBOT0007 folder
var ROBOT_INSTALL = textBox1.Text + #"\" + "911" + #"\" + "files"; //ROBOT0007/911/files
var ROBOT_INSTALL_SPECIAL = ROBOT_INSTALL + #"\" + "special.rar"; //ROBOT0007/911/files/special.rar
//If the path has text...
if (textBox1.TextLength > 0)
{
//if the subfolder doesn't exist then make it.
if (!Directory.Exists(ROBOT0007))
{
Directory.CreateDirectory(ROBOT0007);
}
//if the textbox directory exists
if (Directory.Exists(ROBOT0007))
{
using (ZipFile zip = ZipFile.Read(ROBOT_INSTALL_SPECIAL))
{
zip.ExtractAll(ROBOT0007, ExtractExistingFileAction.OverwriteSilently);
}
}
}
}
UPDATE(04/11/2014): I have removed the textbox for now gone back to simple basics, the following works with a background worker, however the cancel button has no effect on RAR files... any advice?
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Threading;
using System.Text;
using System.Windows.Forms;
using Ionic.Zip;
using System.IO;
namespace BackgroundWorkerSample
{
// The BackgroundWorker will be used to perform a long running action
// on a background thread. This allows the UI to be free for painting
// as well as other actions the user may want to perform. The background
// thread will use the ReportProgress event to update the ProgressBar
// on the UI thread.
public partial class Form1 : Form
{
/// <summary>
/// The backgroundworker object on which the time consuming operation
/// shall be executed
/// </summary>
BackgroundWorker backgroundWorker1;
public Form1()
{
InitializeComponent();
backgroundWorker1 = new BackgroundWorker();
// Create a background worker thread that ReportsProgress &
// SupportsCancellation
// Hook up the appropriate events.
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler
(backgroundWorker1_ProgressChanged);
backgroundWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler
(backgroundWorker1_RunWorkerCompleted);
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = true;
}
/// <summary>
/// On completed do the appropriate task
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// The background process is complete. We need to inspect
// our response to see if an error occurred, a cancel was
// requested or if we completed successfully.
if (e.Cancelled)
{
lblStatus.Text = "Task Cancelled.";
}
// Check to see if an error occurred in the background process.
else if (e.Error != null)
{
lblStatus.Text = "Error while performing background operation.";
}
else
{
// Everything completed normally.
lblStatus.Text = "Task Completed...";
}
//Change the status of the buttons on the UI accordingly
btnStart.Enabled = true;
btnCancel.Enabled = false;
}
/// <summary>
/// Notification is performed here to the progress bar
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// This function fires on the UI thread so it's safe to edit
// the UI control directly, no funny business with Control.Invoke :)
// Update the progressBar with the integer supplied to us from the
// ReportProgress() function.
progressBar1.Value = e.ProgressPercentage;
lblStatus.Text = "Processing......" + progressBar1.Value.ToString() + "%";
}
/// <summary>
/// Time consuming operations go here </br>
/// i.e. Database operations,Reporting
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// The sender is the BackgroundWorker object we need it to
// report progress and check for cancellation.
//NOTE : Never play with the UI thread here...
for (int i = 0; i < 100; i++)
{
//Thread.Sleep(100);
string INSTALL_FOLDER= "C:" + #"\" + "Program Files (x86)" + #"\" + "Robot91111"+ #"\" + "basic" + #"\" + "string" + #"\" + "special.rar";
string BURGOS_FOLDER = "C:" + #"\" + "Program Files (x86)" + #"\" + "Robot91111" + #"\" + "Burgos_Folder";
if (!Directory.Exists(BURGOS_FOLDER))
{
Directory.CreateDirectory(BURGOS_FOLDER);
using (ZipFile zip = ZipFile.Read(INSTALL_FOLDER))
{
zip.ExtractAll(BURGOS_FOLDER, ExtractExistingFileAction.OverwriteSilently);
}
}
// Periodically report progress to the main thread so that it can
// update the UI. In most cases you'll just need to send an
// integer that will update a ProgressBar
backgroundWorker1.ReportProgress(i);
// Periodically check if a cancellation request is pending.
// If the user clicks cancel the line
// m_AsyncWorker.CancelAsync(); if ran above. This
// sets the CancellationPending to true.
// You must check this flag in here and react to it.
// We react to it by setting e.Cancel to true and leaving
if (backgroundWorker1.CancellationPending)
{
// Set the e.Cancel flag so that the WorkerCompleted event
// knows that the process was cancelled.
e.Cancel = true;
backgroundWorker1.ReportProgress(0);
return;
}
}
//Report 100% completion on operation completed
backgroundWorker1.ReportProgress(100);
}
private void btnStartAsyncOperation_Click(object sender, EventArgs e)
{
//Change the status of the buttons on the UI accordingly
//The start button is disabled as soon as the background operation is started
//The Cancel button is enabled so that the user can stop the operation
//at any point of time during the execution
btnStart.Enabled = false;
btnCancel.Enabled = true;
// Kickoff the worker thread to begin it's DoWork function.
backgroundWorker1.RunWorkerAsync();
}
private void btnCancel_Click(object sender, EventArgs e)
{
if (backgroundWorker1.IsBusy)
{
// Notify the worker thread that a cancel has been requested.
// The cancel will not actually happen until the thread in the
// DoWork checks the backgroundWorker1.CancellationPending flag.
backgroundWorker1.CancelAsync();
}
}
}
}
/*...*/
using (ZipFile zip = ZipFile.Read(ROBOT_INSTALL_SPECIAL))
{
zip.ExtractProgress +=
new EventHandler<ExtractProgressEventArgs>(zip_ExtractProgress);
zip.ExtractAll(ROBOT0007, ExtractExistingFileAction.OverwriteSilently);
}
/*...*/
void zip_ExtractProgress(object sender, ExtractProgressEventArgs e)
{
if (e.TotalBytesToTransfer > 0)
{
progressBar1.Value = Convert.ToInt32(100 * e.BytesTransferred / e.TotalBytesToTransfer);
}
}
private int totalFiles;
private int filesExtracted;
/*...*/
using (ZipFile zip = ZipFile.Read(ROBOT_INSTALL_SPECIAL))
{
totalFiles = zip.Count;
filesExtracted = 0;
zip.ExtractProgress += ZipExtractProgress;
zip.ExtractAll(ROBOT0007, ExtractExistingFileAction.OverwriteSilently);
}
/*...*/
private void ZipExtractProgress(object sender, ExtractProgressEventArgs e)
{
if (e.EventType != ZipProgressEventType.Extracting_BeforeExtractEntry)
return;
filesExtracted++;
progressBar.Value = 100 * filesExtracted / totalFiles;
}
using System.IO.Compression;
private async void Unzip(string filePath)
{
var _downloadPath = configuration.GetValue("DownloadPath");
var _extractPath = configuration.GetValue("ExtractPath");
var _extractPattern = configuration.GetValue("ExtractPattern");
Console.WriteLine($"Удаление старых файлов из директории: '{_extractPath}'");
var directoryInfo = new DirectoryInfo(_extractPath);
foreach (var file in directoryInfo.GetFiles())
{
file.Delete();
}
Console.WriteLine($"Распаковка файла: '{filePath}'");
var regex = new Regex(_extractPattern);
var fileList = new List();
var totalFiles = 0;
var filesExtracted = 0;
using (var archive = await Task.Run(() => ZipFile.OpenRead(filePath)))
{
foreach (var file in archive.Entries)
{
if (regex.IsMatch(file.Name))
{
fileList.Add(file);
totalFiles++;
}
}
foreach (var file in fileList)
{
Console.WriteLine($"Извлечение файла: '{file.Name}'");
await Task.Run(() =>
{
file.ExtractToFile($"{_extractPath}{file.Name}");
filesExtracted++;
var progress = Convert.ToInt32(100 * filesExtracted / totalFiles);
Console.WriteLine($"Извлечено: {progress}%");
});
}
}
}
appsettings.json example
{
"DownloadPath": "f:\\download\\",
"ExtractPath": "f:\\download\\extract\\",
"ExtractPattern": "ACTSTAT.DBF|CENTERST.DBF|CURENTST.DBF|ESTSTAT.DBF|FLATTYPE.DBF|NDOCTYPE.DBF|OPERSTAT.DBF|ROOMTYPE.DBF|SOCRBASE.DBF|STRSTAT.DBF|[A-Z]{1}16.DBF",
}
void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
using (ZipFile zip = ZipFile.Read(#"edu.zip"))
{
totalFiles = zip.Count;
filesExtracted = 0;
zip.ExtractProgress += ZipExtractProgress;
zip.ExtractAll(#"./", ExtractExistingFileAction.OverwriteSilently);
}
if (backgroundWorker1.CancellationPending)
{
e.Cancel = true;
backgroundWorker1.ReportProgress(0);
return;
}
backgroundWorker1.ReportProgress(100);
}
private void ZipExtractProgress(object sender, ExtractProgressEventArgs e)
{
if (e.EventType != ZipProgressEventType.Extracting_BeforeExtractEntry)
return;
filesExtracted++;
this.Dispatcher.Invoke(new Action(() =>
{
progressBar1.Value = 100 * filesExtracted / totalFiles;
}));
}
void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
status.Content = "extractia a fost anulata";
}
else if (e.Error != null)
{
status.Content = "Ceva nu a mers ";
}
}
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
status.Content = "Se dezarhiveaza......" + progressBar1.Value.ToString() + "%";
}
private void Button_Click(object sender, RoutedEventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}

Understanding BackgroundWorker

I would like to understand how BackgroundWorker is being used.
I have tried to break it down to a Console C# project, actually it is a Windows Form Application, where a button triggers the execution of three background tasks. When the button is pressed, it should become disabled preventing further button events. When all three tasks have finished, the button should be enabled again. Also the success of the three tasks should be tested in the main thread. To prevent from mixing this all into a Forms app, I'm trying now to understand the basics and move that to the Forms application.
(From the comments settings you may guess where my problems of understanding are)
Consider this (still erroneous) code:
using System;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
namespace BGWorkerConsoleApp
{
class Program
{
// #region BackgroundWorker
//
private System.ComponentModel.BackgroundWorker worker = new System.ComponentModel.BackgroundWorker();
//
// #endregion
static void Main(string[] args)
{
// BackgroundWorker worker = new System.ComponentModel.BackgroundWorker();
worker.RunWorkerCompleted += TestComplete;
worker.DoWork += TestConnection1;
// worker.DoWork += TestConnection2;
// worker.DoWork += TestConnection3;
DoWorkEventArgs e = new DoWorkEventArgs(); // ???
worker.RunWorkerAsync(e); // ???
}
private void TestConnection1(object sender, DoWorkEventArgs e)
{
bool success = false;
Thread.Sleep(14000); // stands for some time consuming Task
success = true;
e.Result = success;
}
private void TestConnection2(object sender, DoWorkEventArgs e)
{
bool success = false;
Thread.Sleep(10000); // stands for some time consuming Task
success = true;
e.Result = success;
}
private void TestConnection3(object sender, DoWorkEventArgs e)
{
bool success = false;
Thread.Sleep(5000); // stands for some time consuming Task
success = true;
e.Result = success;
}
private void TestComplete(object sender, RunWorkerCompletedEventArgs e)
{
Console.WriteLine("complete");
}
}
}
My questions:
Can I add more than one Task to a BackgroundWorker? Probably not, since there is only one RunWorkerAsync. Assuming I need a worker for every task, how would I wait for all three tasks to complete?
OK, let me change it to a Forms application since the stress doesn't lay on running BackGroundworker in a console App, rather I would like to understand how to design the application for more than one BackGroundWorkers and wait for their completion (including the passing and returning of parameters and results.
Here is the code for the same in a Form app:
using System;
using System.Windows.Forms;
using System.ComponentModel;
using System.Threading;
namespace BackGroundWorkerForm
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
private Button button;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Text = "Form1";
button = new System.Windows.Forms.Button();
button.Location = new System.Drawing.Point(100, 10);
button.Name = "button";
button.Size = new System.Drawing.Size(100, 20);
button.TabIndex = 5;
button.Text = "TestConnection";
button.Click += new System.EventHandler(this.RunTest);
button.Visible = true;
Controls.Add(button);
}
#endregion
private void RunTest(object o, EventArgs e)
{
BackgroundWorker worker = new System.ComponentModel.BackgroundWorker();
worker.RunWorkerCompleted += TestComplete;
worker.DoWork += TestConnection1;
// worker.DoWork += TestConnection2;
// worker.DoWork += TestConnection3;
worker.RunWorkerAsync();
}
private void TestConnection1(object sender, DoWorkEventArgs e)
{
bool success = false;
Thread.Sleep(10000); // stands for some time consuming Task
success = true;
e.Result = success;
}
private void TestConnection2(object sender, DoWorkEventArgs e)
{
bool success = false;
Thread.Sleep(10000); // stands for some time consuming Task
success = true;
e.Result = success;
}
private void TestConnection3(object sender, DoWorkEventArgs e)
{
bool success = false;
Thread.Sleep(10000); // stands for some time consuming Task
success = true;
e.Result = success;
}
private void TestComplete(object sender, RunWorkerCompletedEventArgs e)
{
button.Text= "complete";
}
}
}
Here is an example using Tasks instead of a background worker.
Each Task is started asynchronously, and then it waits for all to complete successfully or not before processing the results.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace BGWorkerConsoleApp
{
class Program
{
static Stopwatch sw = new Stopwatch();
static void Main(string[] args)
{
// parse your args
int[] parsedargs = { 1400, 1000, 500 };
int count = 0; // to provide task count
List<Task> tasks = new List<Task>();
sw.Start(); //stopwatch for some to verify the time
foreach (int i in parsedargs)
{
// start a task for each
tasks.Add(Task.Factory.StartNew<bool>(
() => { return myTask(i, String.Format("Task{0} done. ", ++count)); } ) );
}
// wait for all the tasks to complete
Task.WaitAll(tasks.ToArray());
// check the response of each
bool faulted = false;
foreach (Task<bool> t in tasks)
{
if (t.Result == false)
{
faulted = true;
break; //there was a problem so quit looking
}
}
//output some text
if (faulted)
{
Console.WriteLine("There was a problem.");
}
else
Console.WriteLine("complete");
// pause so we can see our output in the debugger
Console.ReadKey();
}
static bool myTask(int time, string msg)
{
Thread.Sleep(time);
if (time == 1000)
return false;
Console.WriteLine(msg + printStopWatchTime());
return true;
}
static string printStopWatchTime()
{
TimeSpan ts = sw.Elapsed;
string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
ts.Hours, ts.Minutes, ts.Seconds,
ts.Milliseconds / 10);
return string.Format("RunTime {0}", elapsedTime);
}
}
}
For your first question: you can reuse a BackgroundWorker as long as you don't try to run the task while it's already runnig (i.e.: IsBusy must equal false). According to MSDN, if you try to do that, it will bite you hard with an InvalidOperationException.
On the second matter: you need some kind of synchronization mechanism to accomplish that. Check WaitHandle.WaitAll(...) and the Barrier class, for instance. There are plenty of options, not just those. You have monitors, semaphores, mutexes and whatnot at your disposal. Do explore the System.Threading namespace.
A BackgroundWorker is good if you want to execute one specific task in the background. That is, the DoWork event is assigned one handler that performs the task and finishes. Of course you can change the event method by removing the old one and assigning a new one, but the important part is that only one task can be performed at a time (you can not have the same background worker run more than once at the same time).
If you want to have two or more tasks to be performed at the same time, you either need more background workers, or you start looking into the Task Parallel Library, as suggested in the comments. Even plain Threads would work.
In either case, as Leandro said in his answer, what you're trying to achive screams for the use of some barrier, as otherwise the first finishing task will enable the button before the other tasks are finished. You, however, want to wait until all the tasks are finished.
I would like to add that in my opinion, use cases for the BackgroundWorker are pretty limited. While it is convenient in some cases, it does not provide the flexibility needed in most cases.

Naudio record microphone for X amount of seconds

Currently I can record the microphone input using the Naudio .dll with this:
public void recordInput()
{
Console.WriteLine("Now recording...");
waveSource = new WaveIn();
waveSource.WaveFormat = new WaveFormat(16000, 1);
waveSource.DataAvailable += new EventHandler<WaveInEventArgs>(waveSource_DataAvailable);
waveSource.RecordingStopped += new EventHandler<StoppedEventArgs>(waveSource_RecordingStopped);
//string tempFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".wav");
string tempFile = Path.Combine(#"C:\Users\Nick\Desktop", "test.wav");
waveFile = new WaveFileWriter(tempFile, waveSource.WaveFormat);
waveSource.StartRecording();
myTimer.Interval = 5000;
myTimer.Tick += new EventHandler(myTimer_Tick);
myTimer.Start();
}
Currently I used an eventhandler to wait for 5 seconds and then onTick I stop recording. However this is causing problems because I am having trouble waiting for the recording task to finish in the main part of the code where I call:
public string voiceToText()
{
recordInput();
//Somehow wait here until record is done then proceed.
convertToFlac();
return "done";
}
I've tried putting the Naudio in a thread and using a waitOne(); but waveSource.StartRecording(); is put in an eventhandler and thread an expection. I've also tried using a Thread.Sleep(5000) and stop recording after that thread finishes but the audio only records the first 500mS of audio for some reason.
I'm pretty new to c# and don't fully understand threading so any help or seperate approach is welcome.
I know this is an old topic, but here is some code that I made for one of my projects and that records audio for x seconds (it uses NAudio.Lame to convert the wave file to mp3):
public class Recorder
{
/// <summary>
/// Timer used to start/stop recording
/// </summary>
private Timer _timer;
private WaveInEvent _waveSource;
private WaveFileWriter _waveWriter;
private string _filename;
private string _tempFilename;
public event EventHandler RecordingFinished;
/// <summary>
/// Record from the mic
/// </summary>
/// <param name="seconds">Duration in seconds</param>
/// <param name="filename">Output file name</param>
public void RecordAudio(int seconds, string filename)
{
/*if the filename is empty, throw an exception*/
if (string.IsNullOrEmpty(filename))
throw new ArgumentNullException("The file name cannot be empty.");
/*if the recording duration is not > 0, throw an exception*/
if (seconds <= 0)
throw new ArgumentNullException("The recording duration must be a positive integer.");
_filename = filename;
_tempFilename = $"{Path.GetFileNameWithoutExtension(filename)}.wav";
_waveSource = new WaveInEvent
{
WaveFormat = new WaveFormat(44100, 1)
};
_waveSource.DataAvailable += DataAvailable;
_waveSource.RecordingStopped += RecordingStopped;
_waveWriter = new WaveFileWriter(_tempFilename, _waveSource.WaveFormat);
/*Start the timer that will mark the recording end*/
/*We multiply by 1000 because the Timer object works with milliseconds*/
_timer = new Timer(seconds * 1000);
/*if the timer elapses don't reset it, stop it instead*/
_timer.AutoReset = false;
/*Callback that will be executed once the recording duration has elapsed*/
_timer.Elapsed += StopRecording;
/*Start recording the audio*/
_waveSource.StartRecording();
/*Start the timer*/
_timer.Start();
}
/// <summary>
/// Callback that will be executed once the recording duration has elapsed
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void StopRecording(object sender, ElapsedEventArgs e)
{
/*Stop the timer*/
_timer?.Stop();
/*Destroy/Dispose of the timer to free memory*/
_timer?.Dispose();
/*Stop the audio recording*/
_waveSource.StopRecording();
}
/// <summary>
/// Callback executed when the recording is stopped
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void RecordingStopped(object sender, StoppedEventArgs e)
{
_waveSource.DataAvailable -= DataAvailable;
_waveSource.RecordingStopped -= RecordingStopped;
_waveSource?.Dispose();
_waveWriter?.Dispose();
/*Convert the recorded file to MP3*/
ConvertWaveToMp3(_tempFilename, _filename);
/*Send notification that the recording is complete*/
RecordingFinished?.Invoke(this, null);
}
/// <summary>
/// Callback executed when new data is available
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void DataAvailable(object sender, WaveInEventArgs e)
{
if (_waveWriter != null)
{
_waveWriter.Write(e.Buffer, 0, e.BytesRecorded);
_waveWriter.Flush();
}
}
/// <summary>
/// Converts the recorded WAV file to MP3
/// </summary>
private void ConvertWaveToMp3(string source, string destination)
{
using (var waveStream = new WaveFileReader(source))
using(var fileWriter = new LameMP3FileWriter(destination, waveStream.WaveFormat, 128))
{
waveStream.CopyTo(fileWriter);
waveStream.Flush();
}
/*Delete the temporary WAV file*/
File.Delete(source);
}
}
And here is how to use it in your code:
var rec = new Recorder();
/*This line allows us to be notified when the recording is complete and the callback 'OnRecordingFinished' will be executed*/
rec.RecordingFinished += OnRecordingFinished;
rec.RecordAudio(seconds, recPath);
private void OnRecordingFinished(object sender, RecordingContentArgs e)
{
//Put your code here to process the audio file
}
If you aren't running in a Windows Forms or WPF application, then you should use WaveInEvent instead, so that a background thread is set up to handle the callbacks. The default constructor for WaveIn uses Windows messages.

Background Worker reports busy despite been cancelled and no longer present

When I cancel and dispose of my background worker, despite it disappearing from the VS threads list it continues to report busy to my application which is waiting for it to close. Also my RunWorkerCompleted event is never raised.
Debugging shows that the code is exited with no further pending execution that I can see.
If I remove the .IsBusy check, VS reports that "This BackgroundWorker is currently busy and cannot run multiple tasks concurrently"
I cannot understand why the thread which is no longer listed in VS continues to operate after closing.
private void UpdateTimeLine()
{
txtb_timeline.Text = "Updating...";
startTimelineUpdater.DoWork += new DoWorkEventHandler(startTimelineUpdater_DoWork);
startTimelineUpdater.RunWorkerAsync("hometimeline");
startTimelineUpdater.WorkerReportsProgress = true;
startTimelineUpdater.ProgressChanged += new ProgressChangedEventHandler
(startTimelineUpdater_ProgressChanged);
startTimelineUpdater.WorkerSupportsCancellation = true;
}
void startTimelineUpdater_DoWork(object sender, DoWorkEventArgs e)
{
//begin the thread to maintain updates of SQL
beginRegUpdateTimeline.DoWork += new DoWorkEventHandler(beginRegUpdateTimeline_DoWork);
beginRegUpdateTimeline.RunWorkerAsync();
beginRegUpdateTimeline.WorkerSupportsCancellation = true;
while (true)
{
List<string[]> sqlset;
Xtweet getSQL = new Xtweet();
if(e.Argument.ToString() == "hometimeline")
{
sqlset = getSQL.CollectLocalTimelineSql();
}
else
{
sqlset = getSQL.CollectLocalTimelineSql(Int64.Parse(e.Argument.ToString()));
}
int i = 0;
while (i < 10)
{
foreach (var stringse in sqlset)
{
StringBuilder sb = new StringBuilder();
sb.Append(stringse[0]);
sb.Append(": ");
sb.Append(stringse[1]);
sb.Append(" # ");
sb.Append(stringse[2]);
sb.Append("\n");
BackgroundWorker bkgwk = sender as BackgroundWorker;
bkgwk.ReportProgress(0, sb.ToString());
Thread.Sleep(1000);
i++;
if(startTimelineUpdater.CancellationPending)
{
e.Cancel = true;
startTimelineUpdater.Dispose();
break;
}
}
if (e.Cancel == true)
{
break;
}
}
if (e.Cancel == true)
{
break;
}
}
}
/// <summary>
/// Handles the DoWork event of the beginRegUpdateTimeline control.
/// Updates the timeline sql on a regular basis
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.ComponentModel.DoWorkEventArgs"/> instance containing the event data.</param>
void beginRegUpdateTimeline_DoWork(object sender, DoWorkEventArgs e)
{
while (true)
{
//update time in seconds
int secs = 10;
Xtweet.PreSqlDataCollection(null,null);
Thread.Sleep(secs*1000);
if(beginRegUpdateTimeline.CancellationPending)
{
e.Cancel = true;
beginRegUpdateTimeline.Dispose();
break;
}
}
}
private void startTimelineUpdater_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
txtb_timeline.Text = e.UserState.ToString();
}
Call Application.DoEvents after thread sleep.
As mentioned here:
the BackgroundWorker simply does not report it is done until the UI
thread gets control.
Update:
Since this is WPF, you should be using Dispatcher

Categories

Resources