Reputation: 3953
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
}
Upvotes: 0
Views: 619
Reputation: 203824
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.
Upvotes: 3