UI freezes on heavy calculation - c#

I am loading huge files to the memory but on this calculation my application is freezes .
Any idea what is the issue with my code ?
public void Drop(DragEventArgs args)
{
BackgroundWorker worker = new BackgroundWorker();
string fileName = IsSingleTextFile(args);
if (fileName == null) return;
worker.DoWork += (o, ea) =>
{
try
{
StreamReader fileToLoad = new StreamReader(fileName);
string filecontent = fileToLoad.ReadToEnd();
fileToLoad.Close();
// providing the information to the UI thread
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
new Action(() => SfmLogFile = filecontent));
}
catch (Exception)
{
throw;
}
};
worker.RunWorkerCompleted += (o, ea) =>
{
args.Handled = true;
IsBusy = false;
};
// Mark the event as handled, so TextBox's native Drop handler is not called.
IsBusy = true;
worker.RunWorkerAsync();
}

I'd transform your sample to something like this:
public void Drop(DragEventArgs args)
{
string fileName = IsSingleTextFile(args);
if (fileName == null) return;
// It is better to create worker after check for file name.
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (o, ea) =>
{
try
{
string filecontent = ReadAllText(fileName);
ea.Result = fileContent;
}
catch (Exception)
{
throw;
}
};
worker.RunWorkerCompleted += (o, ea) =>
{
var fileContent = ea.Result as string;
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
new Action(() => SfmLogFile = filecontent));
// if IsBusy propery is not on the UI thread, then you may leave it here
// otherwise it should be set using the dispatcher too.
IsBusy = false;
};
IsBusy = true;
worker.RunWorkerAsync();
// Mark the event as handled, so TextBox's native Drop handler is not called.
args.Handled = true;
}

I am not sure if it's the cause of your problem, but you are setting args.Handled in a callback for the background worker, so it will be called after the drop event handler has returned. It won't have the desired effect as it's set too late, and it might mess up something in the event handling.

Related

Background worker isn't returning a value

I can't find out what is wrong in the code below. Since Parallel.For is freezing the form, I used a backgroundworker. However, it doesn't return the string
as intended. Also I'm not sure about how to use report for progressbar in backgroundworker. I don't think I need the variable int i; but without it, I can't report the progress.
private string trdoc(string str)
{
string alltrdoc;
var alldoc = str.Split('\n');
string[] alltrdoc1 = new string[alldoc.Length];
pb1.Maximum = alldoc.Length;//progressbar
pb1.Value = 0;
int i = 0;
BackgroundWorker bw = new BackgroundWorker();
// this allows our worker to report progress during work
bw.WorkerReportsProgress = true;
// what to do in the background thread
bw.DoWork += new DoWorkEventHandler(
delegate (object o, DoWorkEventArgs e)
{
Parallel.For(0, alldoc.Length, new ParallelOptions { MaxDegreeOfParallelism = 4 },
index =>
{
alltrdoc1[index] = translate(alldoc[index]);
bw.ReportProgress(i++);
});
e.Result = alltrdoc1;
});
bw.ProgressChanged += new ProgressChangedEventHandler(
delegate (object o, ProgressChangedEventArgs e)
{
if (pb1.Value <= pb1.Maximum)
{
pb1.Value++;
}
});
// what to do when worker completes its task (notify the user)
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(
delegate (object o, RunWorkerCompletedEventArgs e)
{
alltrdoc1 = e.Result as string[];
});
bw.RunWorkerAsync();
alltrdoc = string.Join(Environment.NewLine, alltrdoc1);
saveposcdic();
return alltrdoc;
}
The end of your trdoc() method is where the problem is in your current implementation:
bw.RunWorkerAsync();
alltrdoc = string.Join(Environment.NewLine, alltrdoc1);
saveposcdic();
return alltrdoc;
bw.RunWorkerAsync returns immediately, so alltrdoc will be an empty string. The alltrdoc1 array is only fully populated once RunWorkerCompleted executes. It´s there that you would create alltrdoc and continue.

Having trouble with background worker processes in WPF application

I found a few other articles regarding using background worker which I've linked just below. I used the code examples and attempted to do this to run 3 different SQL Query's. In the code posted below when I break inside of RunBackGroundWorkerProcesses1 it does stop there and is called but method for worker_DoWork1 is never called even though it is in the code. I'm assuming that I've misunderstood this, can someone add some clarity.
Link I used for reference:
WPF Multithreading
Code:
public CallInformationMainScreen()
{
InitializeComponent();
//This is where i call the background processes
RunBackGroundWorkerProcesses1();
RunBackGroundWorkerProcesses2();
RunBackGroundWorkerProcesses3();
}
#endregion
#region Methods used to generate data for the UI
public string DisplayTotalDailyCalls()
{
DailyCallsQuery db = new DailyCallsQuery();
return db.GetNumber(SkillNumber);
}
public string DisplayTotalLastSevenCalls()
{
PrevSevenCallQuery db = new PrevSevenCallQuery();
return db.GetNumber(SkillNumber);
}
public string DisplayDailyAbandonCalls()
{
DailyAbandonQuery db = new DailyAbandonQuery();
return db.GetNumber(SkillNumber);
}
#endregion
#region Background worker processes
private void RunBackGroundWorkerProcesses1()
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork1);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
System.Timers.Timer t = new System.Timers.Timer(10000); // 10 second intervals
t.Elapsed += (sender, e) =>
{
// Don't try to start the work if it's still busy with the previous run...
if (!worker.IsBusy)
worker.RunWorkerAsync();
};
}
private void RunBackGroundWorkerProcesses2()
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork2);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
System.Timers.Timer t = new System.Timers.Timer(10000); // 10 second intervals
t.Elapsed += (sender, e) =>
{
// Don't try to start the work if it's still busy with the previous run...
if (!worker.IsBusy)
worker.RunWorkerAsync();
};
}
private void RunBackGroundWorkerProcesses3()
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork3);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
System.Timers.Timer t = new System.Timers.Timer(10000); // 10 second intervals
t.Elapsed += (sender, e) =>
{
// Don't try to start the work if it's still busy with the previous run...
if (!worker.IsBusy)
worker.RunWorkerAsync();
};
}
private void worker_DoWork1(object sender, DoWorkEventArgs e)
{
// Whatever comes back from the lengthy process, we can put into e.Result
TotalDailyCalls = DisplayTotalDailyCalls();
e.Result = TotalDailyCalls;
}
private void worker_DoWork2(object sender, DoWorkEventArgs e)
{
// Whatever comes back from the lengthy process, we can put into e.Result
TotalDailyLast7Days = DisplayTotalLastSevenCalls();
e.Result = TotalDailyCalls;
}
private void worker_DoWork3(object sender, DoWorkEventArgs e)
{
// Whatever comes back from the lengthy process, we can put into e.Result
TotalDailyAbandon = DisplayDailyAbandonCalls();
e.Result = TotalDailyAbandon;
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// First, handle the case where an exception was thrown.
if (e.Error != null)
{
// handle the System.Exception
MessageBox.Show(e.Error.Message);
}
else if (e.Cancelled)
{
// now handle the case where the operation was cancelled...
ErrorHolder = "The operation was cancelled";
}
else
{
// Finally, handle the case where the operation succeeded
ErrorHolder = e.Result.ToString();
}
}
#endregion
You don't start your timers. See Timer.Start Method ().
private void RunBackGroundWorkerProcesses1()
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork1);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
System.Timers.Timer t = new System.Timers.Timer(10000); // 10 second intervals
t.Elapsed += (sender, e) =>
{
// Don't try to start the work if it's still busy with the previous run...
if (!worker.IsBusy)
worker.RunWorkerAsync();
};
t.Start(); // Start the timer
}
I'm posting this to demonstrate an easier way to do this. It's not meant to be a direct answer to the question.
If you NuGet "System.Reactive" and the associated WPF libraries you can do this:
IDisposable subscription =
new []
{
Observable.Interval(TimeSpan.FromSeconds(10.0)).Select(x => DisplayTotalDailyCalls()),
Observable.Interval(TimeSpan.FromSeconds(10.0)).Select(x => DisplayTotalLastSevenCalls()),
Observable.Interval(TimeSpan.FromSeconds(10.0)).Select(x => DisplayDailyAbandonCalls()),
}
.Merge()
.ObserveOnDispatcher()
.Subscribe(x => ErrorHolder = x, e => MessageBox.Show(e.Error.Message));
That's it. Job done. All of your code in techically one line of code.
BackgroundWorker.RunWorkerAsync() is only called when the Timer.Elapsed event is fired. Since the timer is set to 10 second intervals, the BackgroundWorker won't start for 10 seconds. You probably should call BackgroundWorker.RunWorkerAsync() after creating and initializing it so that it will start right away.

How to remove Application.DoEvents() with better solution like BackgroundWorker in multithreading

This is my code. I am using Application.DoEvents() for waiting that UI thread is finished.
public override void Process(Crawler crawler, PropertyBag propertyBag)
{
AspectF.Define.
NotNull(crawler, "crawler").
NotNull(propertyBag, "propertyBag");
if (propertyBag.StatusCode != HttpStatusCode.OK)
return;
if (!IsHtmlContent(propertyBag.ContentType))
return;
m_Logger.Verbose("CefGlue started for url {0}", propertyBag.ResponseUri.ToString());
CefGlueBrowserForm cefGlueBrowserForm = new CefGlueBrowserForm(propertyBag.ResponseUri.ToString());
cefGlueBrowserForm.Show();
while (!cefGlueBrowserForm.Done)
Application.DoEvents();
string htmlSource = cefGlueBrowserForm.DocumentDomHtml;
propertyBag.GetResponse = () => new MemoryStream(Encoding.UTF8.GetBytes(htmlSource));
base.Process(crawler, propertyBag);
}
I am reading that Application.DoEvents() is evil. I am also getting sometimes stackoverflow exception. What to use instead of Application.DoEvents()?
I try something with BackgroundWorker but nothing works
Example:
AutoResetEvent waitHandle = new AutoResetEvent(false);
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.DoWork += (sender, e) =>
{
if (!e.Cancel)
{
CefGlueBrowserForm cefGlueBrowserForm = new CefGlueBrowserForm(propertyBag.ResponseUri.ToString());
cefGlueBrowserForm.Show();
while (!cefGlueBrowserForm.Done)
Application.DoEvents();
e.Result = cefGlueBrowserForm.DocumentDomHtml;
cefGlueBrowserForm.Dispose();
waitHandle.Set();
}
};
bw.RunWorkerCompleted += (sender, e) =>
{
string htmlSource = e.Result.ToString();
propertyBag.GetResponse = () => new MemoryStream(Encoding.UTF8.GetBytes(htmlSource));
base.Process(crawler, propertyBag);
};
bw.RunWorkerAsync(TimeSpan.FromSeconds(10));
waitHandle.WaitOne(TimeSpan.FromSeconds(20));
What can I do?
EDIT:
added code how cefGlueBrowserForm.Done is set.
public partial class CefGlueBrowserForm : Form
{
public CefGlueBrowserForm(string url)
{
m_Logger = NCrawlerModule.Container.Resolve<ILog>();
m_url = url;
InitializeComponent();
CefManager.InitializeCef();
AddWebBrowser(m_url);
}
private void AddWebBrowser(string startUrl)
{
m_textBox = new TextBox
{
Dock = DockStyle.Bottom,
ReadOnly = true,
};
m_textBox.Parent = this;
Console.Box = m_textBox;
Console.WriteLine("Loading URL ...");
CefGlueBrowser = new ChromiumCefWebBrowser();
CefGlueBrowser.Dock = DockStyle.Fill;
CefGlueBrowser.BringToFront();
CefGlueBrowser.StartUrl = startUrl;
CefGlueBrowser.Parent = this;
Controls.Add(CefGlueBrowser);
Console.WriteLine("URL " + startUrl + " loaded.");
CefGlueBrowser.LoadEnd += Browser_LoadEnd;
}
private void Browser_LoadEnd(object sender, EventArgs e)
{
m_Logger.Verbose("Page load was ended for url {0}", m_url);
MyCefStringVisitor visitor = new MyCefStringVisitor(this, m_url);
((LoadEndEventArgs)e).Frame.Browser.GetMainFrame().GetSource(visitor);
}
private class MyCefStringVisitor : CefStringVisitor
{
#region Instance Fields
private CefGlueBrowserForm m_cefGlueBrowserForm;
private string m_url;
private ILog m_Logger;
#endregion
#region Constructors
public MyCefStringVisitor(CefGlueBrowserForm cefGlueBrowserForm, string url)
{
m_Logger = NCrawlerModule.Container.Resolve<ILog>();
m_cefGlueBrowserForm = cefGlueBrowserForm;
m_url = url.NormalizeUrl();
}
#endregion
#region Instance Methods
protected override void Visit(string value)
{
string currentUrl = m_cefGlueBrowserForm.CefGlueBrowser.Browser.GetMainFrame().Url.NormalizeUrl();
if (m_url.Equals(currentUrl, StringComparison.OrdinalIgnoreCase) || currentUrl.Contains(m_url))
{
m_Logger.Verbose("Getting html source for url {0} and closing Event", m_url);
m_cefGlueBrowserForm.DocumentDomHtml = value;
m_cefGlueBrowserForm.Done = true;
m_cefGlueBrowserForm.CefGlueBrowser.LoadEnd -= m_cefGlueBrowserForm.Browser_LoadEnd;
}
}
#endregion
}
#endregion
}
So what you want to do is execute the rest of the code after the form has been closed. You can do that by simply subscribing to the FormClosed event and running the rest of that code there, rather than blocking the UI thread until the form is closed:
CefGlueBrowserForm cefGlueBrowserForm =
new CefGlueBrowserForm(propertyBag.ResponseUri.ToString());
cefGlueBrowserForm.Show();
cefGlueBrowserForm.FormClosed += (sender, e) =>
{
string htmlSource = cefGlueBrowserForm.DocumentDomHtml;
propertyBag.GetResponse = () =>
new MemoryStream(Encoding.UTF8.GetBytes(htmlSource));
base.Process(crawler, propertyBag);
};
There's no need for other threads to do this.
Another approach is to leverage the await keyword to accomplish this same general task, but with a generally easier syntax. If we take the time to write a simple helper method that generates a Task that will be completed when the form is closed:
public static Task WhenClosed(this Form form)
{
var tcs = new TaskCompletionSource<bool>();
FormClosedEventHandler handler = null;
handler = (s, args) =>
{
tcs.TrySetResult(true);
form.FormClosed -= handler;
};
form.FormClosed += handler;
return tcs.Task;
}
Then we can write the method like this:
public async Task Process(Crawler crawler, PropertyBag propertyBag)
{
//...
CefGlueBrowserForm cefGlueBrowserForm =
new CefGlueBrowserForm(propertyBag.ResponseUri.ToString());
cefGlueBrowserForm.Show();
await cefGlueBrowserForm.WhenClosed();
string htmlSource = cefGlueBrowserForm.DocumentDomHtml;
propertyBag.GetResponse = () =>
new MemoryStream(Encoding.UTF8.GetBytes(htmlSource));
base.Process(crawler, propertyBag);
}
And while this looks like it's blocking until the form is closed, under the hood this is generating code that will act rather similarly to the code that we have above; it will run everything after the await as a continuation fired when the event fires.

Showing busy indicator control inside a UI

I have modified code, but now I have another problem. The InvalidOperation exception occurs inside if statement on validating user info. It says that the calling thread cannot access this object because a different thread owns it.Any sugestions?
private void finishConfigButton_Click(object sender, RoutedEventArgs e)
{
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
bool validated = false;
errorLabel.Visibility = System.Windows.Visibility.Collapsed;
validationProfile.IsBusy = true;
finishConfigButton.IsEnabled = false;
backToLoginHyperlink.IsEnabled = false;
worker.DoWork += (o, ea) =>
{
if (newUser.ValidateNewUserInformation(newNameTextBox.Text, newEmailTextBox.Text, newUsernameTextBox.Text, newPasswordPasswordBox.Password, ref errorLabel))
{
validated = true;
string activeDir = Environment.SystemDirectory.Substring(0, 1) + #":\Users\" + Environment.UserName + #"\My Documents\SSK\Users";
string newPath = System.IO.Path.Combine(activeDir, newUser.Username);
Directory.CreateDirectory(newPath);
newUser.SaveUserData(newUser);
newPath = System.IO.Path.Combine(activeDir, newUser.Username + #"\Settings");
Directory.CreateDirectory(newPath);
newUserSettings.SetDefaultValues();
newUserSettings.SaveSettings(newUser, newUserSettings);
}
else
validated = false;
if (worker.CancellationPending)
{
ea.Cancel = true;
return;
}
};
worker.RunWorkerCompleted += (o, ea) =>
{
validationProfile.IsBusy = false;
finishConfigButton.IsEnabled = true;
backToLoginHyperlink.IsEnabled = true;
};
worker.RunWorkerAsync(this);
if (validated)
{
IntelliMonitorWindow intelliMonitor = new IntelliMonitorWindow(newUser, newUserSettings);
intelliMonitor.Show();
this.Close();
}
else
errorLabel.Visibility = System.Windows.Visibility.Visible;
}
What you are doing here is running everything on the UI thread. This means that while the heavy code is running, you are blocking the UI from repainting, and hence the validationProfile is not updated untill the end of the method, where IsBusy is set to false.
What you need to do is to process the heavy code into a new thread, which can update the UI at the same time.
Take a look at this blog post written by Brian Lagunas, the creator of Extended Toolkit:
http://elegantcode.com/2011/10/07/extended-wpf-toolkitusing-the-busyindicator/
He explains how to use the BusyIndicator with a BackgroundWorker.
The busy indicator in your XAML code does not have any content. Put some control(s) into it:
<wpfet:BusyIndicator Name="validationProfile" IsBusy="False" BusyContent="Working...Please wait" DisplayAfter="0" Background="DimGray">
<Grid>
...
</Grid>
</wpfet:BusyIndicator>
If you change to busy, those controls will get disabled and the BusyIndicator will appear above them.
I suppose you want to wrap the whole <Grid Background="LightGray"> with the BusyIndicator.
Use a background worker or a new thread to run the heavy process and set the UI thread free. This helps you to update the UI even when the background process is running
Eg:
public void finishConfigButton_Click()
{
worker = new BackgroundWorker();
worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
//Do the heavy work here
};
worker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
{
//Things to do after the execution of heavy work
validationProfile.IsBusy = false;
};
validationProfile.IsBusy= true;
worker.RunWorkerAsync();
}
}
I finally figure it out. You can't use UI objects inside worker.DoWork block.I slightly modified the code and it now works.
private void finishConfigButton_Click(object sender, RoutedEventArgs e)
{
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
errorLabel.Visibility = System.Windows.Visibility.Collapsed;
validationProfile.IsBusy = true;
finishConfigButton.IsEnabled = false;
backToLoginHyperlink.IsEnabled = false;
bool validated = false;
string newName = newNameTextBox.Text;
string newEmail = newEmailTextBox.Text;
string newUsername = newUsernameTextBox.Text;
string newPassword = newPasswordPasswordBox.Password;
string errorMessage = "Unknown error.";
worker.DoWork += (o, ea) =>
{
if (newUser.ValidateNewUserInformation(newName, newEmail, newUsername, newPassword, ref errorMessage))
{
string activeDir = Environment.SystemDirectory.Substring(0, 1) + #":\Users\" + Environment.UserName + #"\My Documents\SSK\Users";
string newPath = System.IO.Path.Combine(activeDir, newUser.Username);
Directory.CreateDirectory(newPath);
newUser.SaveUserData(newUser);
newPath = System.IO.Path.Combine(activeDir, newUser.Username + #"\Settings");
Directory.CreateDirectory(newPath);
newUserSettings.SetDefaultValues();
newUserSettings.SaveSettings(newUser, newUserSettings);
validated = true;
}
else
ea.Cancel = true;
};
worker.RunWorkerCompleted += (o, ea) =>
{
if (validated)
{
IntelliMonitorWindow intelliMonitor = new IntelliMonitorWindow(newUser, newUserSettings);
intelliMonitor.Show();
this.Close();
}
validationProfile.IsBusy = false;
finishConfigButton.IsEnabled = true;
backToLoginHyperlink.IsEnabled = true;
errorLabel.Visibility = System.Windows.Visibility.Visible;
errorLabel.Content = errorMessage;
};
worker.RunWorkerAsync();
}

How can I wait for a signal before proceeding compilation of other code segment

Can I wait for a signal from an event so that, when I receive the signal then only I will proceed with next code segment?
For making it clear , I have the follwoing code:
hiddenMediaElement.Source = new Uri(strMediaFileName, UriKind.RelativeOrAbsolute);
hiddenMediaElement.MediaFailed += (obj, Sender) =>
{
bMediaError = true;
};
if (!bMediaError)
{
ObjChildMediaPlayer.Visibility = Visibility.Visible;
ObjChildMediaPlayer._currenTitle = strTitle;
ObjChildMediaPlayer.Show();
Content_FullScreenChanged(null, null);
}
The problem here is the if condition is executed before the MediaFailed event. But I want to wait for MediaFailed event to be executed 1st and then the if condition and I do not want to use events here.
How could I wait for the same? Can I use a mutex or something similar?
You can use AutoResetEvent to handle this situation. But I definitly would try to find another way if there is one.
var autoResetEvent = new AutoResetEvent(false);
hiddenMediaElement.Source = new Uri(strMediaFileName, UriKind.RelativeOrAbsolute);
hiddenMediaElement.MediaFailed += (obj, Sender) =>
{
bMediaError = true;
autoResetEvent.Set();
};
hiddenMediaElement.MediaOpened += (obj, Sender) =>
{
// I think this occurs when it is successfull. Else put it in the handler that handles a success
autoResetEvent.Set();
};
autoResetEvent.WaitOne(); // set a timeout value
if (!bMediaError)
{
ObjChildMediaPlayer.Visibility = Visibility.Visible;
ObjChildMediaPlayer._currenTitle = strTitle;
ObjChildMediaPlayer.Show();
Content_FullScreenChanged(null, null);
}
Or ... I'm not sure this will work, but try it out.
hiddenMediaElement.Source = new Uri(strMediaFileName, UriKind.RelativeOrAbsolute);
hiddenMediaElement.MediaOpened += (obj, sender) =>
{
ObjChildMediaPlayer.Visibility = Visibility.Visible;
ObjChildMediaPlayer._currenTitle = strTitle;
ObjChildMediaPlayer.Show();
Content_FullScreenChanged(null, null);
};
Put your code in the event handler:
hiddenMediaElement.Source = new Uri(strMediaFileName, UriKind.RelativeOrAbsolute);
hiddenMediaElement.MediaFailed += (obj, Sender) =>
{
ObjChildMediaPlayer.Visibility = Visibility.Visible;
ObjChildMediaPlayer._currenTitle = strTitle;
ObjChildMediaPlayer.Show();
Content_FullScreenChanged(null, null);
};

Categories

Resources