views:

49

answers:

2

Here is my scenario: On a form I have list of direcotories, Button and control to display multiline text. In a loop I try to find all files in each directory and delete them. When file is deleted i want to add text to multiline control. My problem is that when text is added I can not do anything else. Form is blocked and if I try do do anytching it just stops responding. Files are deleted using BackgroundWorker

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
//this is datatable with directories and other info
        MainDataset.CZYSZCZENIEDataTable CZYSZCZENIE = e.Argument as MainDataset.CZYSZCZENIEDataTable;
        CzyscPliki(CZYSZCZENIE, ReportProgress);
    }

private void CzyscPliki(MainDataset.CZYSZCZENIEDataTable CZYSZCZENIE, ReportProgressDel del)
    {
        DirectoryInfo dir = null;
        FileInfo[] files = null;
        bool subfolder = false;
        string katalog = "";
        string maska = "";
        string[] maski = null;
        long total=0;

        string dirS;
        string fileS;
        long fileLen;
//foreach directory to delete
        foreach (DataRow r in CZYSZCZENIE.Rows)
        {
//CanRead - check if row is not deleted or detached
//r["CZYSC"].AsBool() - check if directory should be cleared
            if (r.CanRead() && r["CZYSC"].AsBool())
            {
                subfolder = r["PODKATALOGI"].AsBool();
                katalog = r["KATALOG"].AsString().TrimEnd('\\');
                maska = r["MASKA"].AsString();
                if (maska.IsEmpty())
                    maska = "*";
                maski = maska.Split(';');
                dir = new DirectoryInfo(katalog);
                if (dir.Exists)
                {
                    foreach (string s in maski)
                    {
                        files = dir.GetFiles(s, (subfolder ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly));
                        dir.GetFiles();
                        foreach (FileInfo f in files)
                        {
                            dirS = f.Directory.FullName;
                            fileS = f.Name;
                            fileLen = f.Length;
                            try
                            {
                                f.Delete();
                                total += fileLen;
                                if (del != null)
//here is problem: del - delegate to report state
//when it is called it blocks form
                                    del(dirS, fileS, fileLen, total);

                            }
                            catch (Exception ex) 
                            { }
                        }
                    }
                }
            }
        }
    }
//this is the delegate that appends text in multiline control
//memoEdit1 is the control
//ceReportProgress.Checked - check if report should be added
private void ReportProgress(string directory, string file, long size, long totalSize)
    {
        if (memoEdit1.InvokeRequired)
        {
            memoEdit1.BeginInvoke(new Action<string, string, long, long>(ReportProgress), directory, file, size, totalSize);
        }
        else
        {
            if (ceReportProgress.Checked)
            {
                if (file.IsEmpty())
                    memoEdit1.AppendText("\r\nCzyszczenie katalogu " + directory);
                else
                {
                    memoEdit1.AppendText(file);
                    if (size > 0)
                    {
                        if (size > 1048576)
                        {
                            decimal d = size / 1048576;
                            d = decimal.Round(d, 2);
                            memoEdit1.AppendText("\tWielkość : " + d.AsString() + " megabajtów", false);
                        }
                        else if (size > 1024)
                        {
                            decimal d = (decimal)size / (decimal)1024;
                            d = decimal.Round(d, 2);
                            memoEdit1.AppendText("\tWielkość : " + d.AsString() + " kilobajtów", false);
                        }
                        else
                            memoEdit1.AppendText("\tWielkość : " + size.AsString() + " bajtów", false);
                    }
                    if (totalSize > 0)
                    {
                        if (totalSize > 1073741824)
                        {
                            decimal d = (decimal)totalSize / (decimal)1073741824;
                            d = decimal.Round(d, 2);
                            memoEdit1.AppendText("Zwolniono dotychczas : " + d.AsString() + " gigabajtów");
                        }
                        else if (totalSize > 1048576)
                        {
                            decimal d = (decimal)totalSize / (decimal)1048576;
                            d = decimal.Round(d, 2);
                            memoEdit1.AppendText("Zwolniono dotychczas : " + d.AsString() + " megabajtów");
                        }
                        else if (totalSize > 1024)
                        {
                            decimal d = (decimal)totalSize / (decimal)1024;
                            d = decimal.Round(d, 2);
                            memoEdit1.AppendText("Zwolniono dotychczas : " + d.AsString() + " kilobajtów");
                        }
                        else
                            memoEdit1.AppendText("Zwolniono dotychczas : " + totalSize.AsString() + " bajtów");
                    }
                }
//scroll to the end of control
                memoEdit1.ScrollToEnd();
            }
        }
    }

How can I improve this to make it not blocking the form?

A: 

If this worker is running asynchronously, then you can have a form which responds to you.

Besides, problems:

  1. You are running the loop in another function - it makes the operation non-reponsive.

  2. You are not even checking if user wants to cancel (just a point i wanted to make) - Handle DoWorkEventArgs's Cancel property inside the foreach loop.

Move the function CzyscPliki's code in the backgroundWorker1_DoWork (it's anyway too tiny).

EDIT:

If you don't want to move the code into DoWork event handler, then better use Thread for more control. I'm not an expert on it but you will find plenty of code on how to implement so.

Nayan
But CzyscPliki is already being called from backgroundWorker1_DoWork, so it is already being executed on a different thread.
kevev22
That's not the point. The problem is looping in external function, not that the external function is not called.
Nayan
function CzyscPliki is used also somewhere else, and even if I move it's code in backgroundWorker1_DoWork it still doesn't work. Can you please give me a sample code that will use Background Worker? Lets say that in while(true) loop worker is changing forms text to current time. This ofcourse requiers cancelation
Pawel
Ok.. this is msdn link only.. I don't have a ready made code right now... http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker%28v=VS.100%29.aspx
Nayan
Do let me know the result... if I am wrong, I want to know.
Nayan
It still doesn't work correctly. the thing is with text control. If I add text to it it blocks my form. If I comment adding text it works ok. I have tried using BackgroundWorker.ProgressChanged but with no luck. Is it possible that there is something wrong with the control?
Pawel
Though it might sound common solution, try Hans advice and put a Thread.Sleep(50) inside the foreach loop.
Nayan
+2  A: 

You are calling ReportProgress too often. Do it more than about 1000 times per second and the UI thread gets flooded with requests that it cannot keep up with. It won't get around to doing its normal duties, which include painting the controls and responding to the mouse and keyboard. It looks frozen. This gets worse when the UI update code gets more expensive, updating text in a TextBox when there's already a lot of text in it can get quite slow.

The diagnostic is still seeing the UI frozen for a while after the BGW stops running, working on emptying the backlog in the invoke request queue, then suddenly jumping back alive when the queue is finally emptied.

You need to throttle the rate at which you call BeginInvoke(). It never makes more sense to call it any more frequently than once every 50 milliseconds, a human cannot perceive the difference beyond that. Collect the info in a List<> so you can BeginInvoke() a lot less frequently. That's still no complete guarantee if your worker can produce results faster than the UI thread could ever keep up with. In which case slowing down the worker would be a fix. Easy by using Invoke instead of BeginInvoke.

Hans Passant
Great point, Hans! I missed that :) +1
Nayan
Thanks :) I have added Thread.Sleep(10) before calling backgroundWorker1.ReportProgress and it works quite well. i wil also add results to List<> so backgroundWorker1.ReportProgress will be called less often
Pawel