How to use async task as a method with parameters - c#

I get confused, when tried to use async task. Everything is fine if I use this method without parameters, but when I add parameters to it, it says that cannot convert System.Threading.Tasks.Task to System.Func. So how to use it properly and fix this error?
Main:
Timers t = new Timers();
public Form1()
{
InitializeComponent();
t.StartTimerTemplate(TEMPLATEupdateSong("test", "test", "test"));//error occurs in this line
}
async Task TEMPLATEupdateSong(string url, string sender, string labelName)
{
string nowPlaying = await getter.getString(url);
nowPlaying = XDocument.Parse(nowPlaying).ToString();
var outputLabel = this.Controls.OfType<Label>()
.FirstOrDefault(control => control.Name == labelName);
outputLabel.Invoke(new Action(() =>
outputLabel.Text = sender + "\n" + nowPlaying
));
}
Timers.cs:
public void StartTimerTemplate(Func<Task> updateTemplate)
{
System.Timers.Timer timer = new System.Timers.Timer(50000);
timer.AutoReset = true;
timer.Elapsed += (sender, e) => timer_Elapsed(sender, e, updateTemplate());
timer.Start();
}
async void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e, Task task)
{
await task;
}

Your method expects a Func<Task>, not a Task.
t.StartTimerTemplate(() => TEMPLATEupdateSong("test", "test", "test"));
Or
t.StartTimerTemplate(async () => await TEMPLATEupdateSong("test", "test", "test"));

Related

Using await Task inside backgroundWorker is changing my status to complete

I am using a loop each 7 seconds. This loop communicates with a server. Then when server is answering i am waiting user to answer about servers response. So i need to check if all this process has finished.
So i am creating a timer
var backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += (sender, e) => backgroundWorker_DoWork(sender, e, frm);
var timer = new System.Timers.Timer();
timer.Enabled = true;
timer.Interval = 10000;
timer.Elapsed += (sender, e) => Timer_Elapsed(sender, e);
timer.Start();
private static void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (!backgroundWorker.IsBusy)
{
backgroundWorker.RunWorkerAsync();
}
}
private static async void backgroundWorker_DoWork(object sender, DoWorkEventArgs e, Main frm)
{
using (var client = new HttpClient())
{
var result = await client.GetAsync(uri);
//message to user for answering, then return it to server again
}
}
For some reason when i am using await..., 'backgroundWorker.IsBusy' flag turns to false. I mean that i can't have correct result if task has actually finished. Should i use a flag for this? or there is more efficient way?
You can't use await inside a BackgroundWorker's DoWork. I recommend replacing BackgroundWoker completely with Task.Run:
Task task = null;
var timer = new Timer();
timer.Interval = 10000;
timer.Tick += Timer_Tick;
timer.Start();
private static async void Timer_Tick(object sender, EventArgs e)
{
if (task == null)
{
task = Task.Run(() => DoWorkAsync(frm));
try { await task; }
finally { task = null; }
}
}
private static async Task DoWorkAsync(Main frm)
{
using (var client = new HttpClient())
{
var result = await client.GetAsync(uri);
//message to user for answering, then return it to server again
}
}
Side note: I changed System.Timers.Timer to System.Windows.Forms.Timer so that the task variable is always accessed from the UI thread.

Cancelling a background worker immediately in the middle of an operation

I am trying to understand what I can and cant do with background workers. I have seen a fair amount of posts on this but it all seems to involve some operation with loops and you cancel an operation within a loop. I am wanting to find out if I can cancel some operation on a background worker without a loop.
I have the following simple form that I'm playing with:
which contains the following code:
string[,] TestData = new string[300000, 100];
List<string> TestDataList;
private static Random random = new Random();
public Form1()
{
InitializeComponent();
// Loading up some fake data
for (int i = 0; i < 300000; i++)
{
for (int j = 0; j < 100; j++)
{
this.TestData[i, j] = RandomString(10) + j.ToString();
}
}
}
public static string RandomString(int length)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[random.Next(s.Length)]).ToArray());
}
which loads a string array with a lot of dummy data. The start button method is as follows:
private void StartWork_Click(object sender, EventArgs e)
{
try
{
System.Threading.SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_Complete);
bw.RunWorkerAsync();
}
catch (Exception ex)
{
MessageBox.Show("Something went wrong.\nError:" + ex.Message);
}
}
And I also have:
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
this.TestDataList = this.TestData.Cast<string>()
.Select((s, i) => new { GroupIndex = i / 100, Item = s.Trim().ToLower() })
.GroupBy(g => g.GroupIndex)
.Select(g => string.Join(",", g.Select(x => x.Item))).ToList();
}
private void bw_Complete(object sender, RunWorkerCompletedEventArgs e)
{
this.showWorkingLabel.Text = "Work done";
}
private void btnCancel_Click(object sender, EventArgs e)
{
// I want to cancel the work with this button
// Then show
this.showWorkingLabel.Text = "Work Cancelled";
}
So you'll notice that my bw_DoWork method does not contain any loops, just a single operation and I want to know if:
If I can kill/cancel the background worker by clicking the Cancel button while the following code is being executed:
.Select((s, i) => new { GroupIndex = i / 100, Item = s.Trim().ToLower() })
.GroupBy(g => g.GroupIndex)
.Select(g => string.Join(",", g.Select(x => x.Item))).ToList();
Can I update the label showWorkingLabel while the background work is happening so that it continuously shows ".", "..", "..." and then back to "." like a progress bar to indicate work is still happening
You need first to support cancellation
bw.WorkerSupportsCancellation = true;
Then you need to share a cancellation token at form level
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken cancellationToken;
Inside your work you need to throw the cancellation:
cancellationToken.ThrowIfCancellationRequested();
Or handle it gracefully with the background worker even for pending cancellations: BackgroundWorker.CancellationPending
And in the cancell button you can call the cancellation like this:
cts.Cancel();
Using your code it will become something similar to the following indication, you should handle graceful cancellations:
string[,] TestData = new string[30000, 100];
List<string> TestDataList;
private static Random random = new Random();
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken cancellationToken;
private void BtnStart_Click(object sender, EventArgs e)
{
try
{
this.showWorkingLabel.Text = "Work start";
System.Threading.SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerSupportsCancellation = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
cancellationToken = cts.Token;
cancellationToken.Register(bw.CancelAsync);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_Complete);
bw.RunWorkerAsync();
}
catch (Exception ex)
{
MessageBox.Show("Something went wrong.\nError:" + ex.Message);
}
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
cancellationToken.ThrowIfCancellationRequested();
this.TestDataList = this.TestData
.Cast<string>()
.Select((s, i) => new { GroupIndex = i / 100, Item = s.Trim().ToLower() })
.GroupBy(g => g.GroupIndex)
.Select(g =>
{
cancellationToken.ThrowIfCancellationRequested();
return string.Join(",", g.Select(x => x.Item));
})
.ToList();
}
private void btnCancel_Click(object sender, EventArgs e)
{
cts.Cancel();
this.showWorkingLabel.Text = "Work Cancelled";
}
As per the MSDN page for BackgroundWorker:
When creating the worker, you can make it support cancellation by setting
backgroundWorker.WorkerSupportsCancellation = true;
You can request cancellation by calling CancelAsync() on the BackgroundWorker.
Then your BackgroundWorker should periodically check the BackgroundWorker.CancellationPending property, and if set to true, it should cancel its operation. As Sir Rufo pointed out in a comment, don't forget to inside the DoWork delegate you have to set DoWorkEventArgs.Cancel to true.
The MSDN page I linked has additional examples of usage in real code.
Here is a working example using the BackgroundWorker builtin cancellation support.
// We need to remember the BackgroundWorker
private BackgroundWorker bw;
private void StartWork_Click( object sender, EventArgs e )
{
bw = new BackgroundWorker
{
WorkerSupportsCancellation = true,
};
bw.DoWork += Bw_DoWork;
bw.RunWorkerCompleted += Bw_RunWorkerCompleted;
bw.RunWorkerAsync();
showWorkingLabel.Text = "Work started ...";
}
private void Bw_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e )
{
if ( e.Cancelled ) // was it cancelled?
{
showWorkingLabel.Text = "Work cancelled.";
return;
}
if ( e.Error != null ) // any error?
{
showWorkingLabel.Text = "Work faulted - " + e.Error.Message;
return;
}
// assign the bw Result to the field
this.TestDataList = (List<string>)e.Result;
showWorkingLabel.Text = "Work completed.";
}
private void Bw_DoWork( object sender, DoWorkEventArgs e )
{
try
{
e.Result = this.TestData
.Cast<string>()
.Select( ( s, i ) =>
{
// check for cancellation
if ( bw.CancellationPending )
throw new OperationCanceledException();
return new
{
GroupIndex = i / 100,
Item = s.Trim().ToLower()
};
} )
.GroupBy( g => g.GroupIndex )
.Select( g =>
{
// check for cancellation
if ( bw.CancellationPending )
throw new OperationCanceledException();
return string.Join( ",", g.Select( x => x.Item ) );
} )
.ToList();
}
catch ( OperationCanceledException )
{
e.Cancel = true;
}
}
private void btnCancel_Click( object sender, EventArgs e )
{
// request cancellation
bw.CancelAsync();
showWorkingLabel.Text = "Work cancellation requested ...";
}
and another doing exactly the same with the modern async/await Task and CancellationToken
private CancellationTokenSource cts;
private async void StartWork_Click( object sender, EventArgs e )
{
showWorkingLabel.Text = "Work started ...";
cts = new CancellationTokenSource();
var token = cts.Token;
try
{
TestDataList = await Task.Run( () =>
{
return this.TestData
.Cast<string>()
.Select( ( s, i ) =>
{
token.ThrowIfCancellationRequested();
return new
{
GroupIndex = i / 100,
Item = s.Trim().ToLower()
};
} )
.GroupBy( g => g.GroupIndex )
.Select( g =>
{
token.ThrowIfCancellationRequested();
return string.Join( ",", g.Select( x => x.Item ) );
} )
.ToList();
}, token );
showWorkingLabel.Text = "Work completed.";
}
catch ( OperationCanceledException )
{
showWorkingLabel.Text = "Work canceled.";
}
catch ( Exception ex )
{
showWorkingLabel.Text = "Work faulted - " + ex.Message;
}
}
private void btnCancel_Click( object sender, EventArgs e )
{
cts.Cancel();
showWorkingLabel.Text = "Work cancellation requested ...";
}

Converting threaded to multi threaded code? ( task factory ?)

I have the following threaded code ( i think ) form with a start and cancel button and a multilined text box, the commented sections (//) are from the working single threaded version and below i have tried to retrofit the multithreaded task.factory parts, however it seems to launch the powershell commands fine ( show in task manager ) but the program completes without waiting for the results from each "heavyOperation".
The idea is to start all the four HeavyOperation tasks at the same time (ish) and wait for each to return the results and append the results to the text box
public partial class Form1 : Form
{
Progress<string> progressReporter = new Progress<string>();
CancellationTokenSource cancelSource;
public Form1()
{
InitializeComponent();
progressReporter.ProgressChanged += progressManager_ProgressChanged;
}
async private void btnStart_Click(object sender, EventArgs e)
{
btnStart.Enabled = false;
btnCancel.Enabled = true;
cancelSource = new CancellationTokenSource();
//textBox1.Text = await Task.Run(() => PerfromTaskAction(cancelSource.Token));
await Task.Run(() => PerfromTaskAction(cancelSource.Token));
lblStatus.Text = "Completed."; btnStart.Enabled = true;
btnCancel.Enabled = false;
}
//private string PerfromTaskAction(CancellationToken ct)
static void PerfromTaskAction(CancellationToken ct)
{
//StringBuilder sb = new StringBuilder();
object[] arrObjects = new object[] { "SERVER1", "SERVER2", "SERVER3", "SERVER4" };
foreach(object i in arrObjects)
{
//if (ct.IsCancellationRequested) break;
//sb.Append(string.Format("{0}: {1}\r\n", HeavyOperation(i.ToString()),i));
//((IProgress<string>)progressReporter).Report(string.Format("Now Checking: {0}...", i));
Task.Factory.StartNew(() => HeavyOperation(i.ToString()));
}
//return sb.ToString();
}
void progressManager_ProgressChanged(object sender, string e)
{
lblStatus.Invoke((Action)(() => lblStatus.Text = e));
}
//private string HeavyOperation(string i)
public static void HeavyOperation(string i)
{
PowerShell ps = PowerShell.Create();
ps.AddCommand("invoke-command");
ps.AddParameter("computername", i);
ps.AddParameter("scriptblock", ScriptBlock.Create("get-vmreplication | select State"));
Collection<PSObject> result = ps.Invoke();
//return result[0].Properties["State"].Value.ToString();
Console.Write(result[0].Properties["State"].Value.ToString());
}
private void btnCancel_Click(object sender, EventArgs e)
{
cancelSource.Cancel();
}
}
Thanks for looking
You also need to await for the HeavyOperations to complete.
You can use Task.WhenAll for this purpose. Here is an async version of PerformTaskAction, using Task.WhenAll
I've taken into account Scott Chamberlain's suggestions:
Changed the unsafe (in async-await context) Task.Factory.StartNew() to Task.Run()
Removed the unnecessary await in the invocation of PerformTaskAction()
Passed the missing CancellationToken in the outer Task.Run() call
static async Task PerfromTaskAction(CancellationToken ct) {
//StringBuilder sb = new StringBuilder();
object[] arrObjects = new object[] { "SERVER1", "SERVER2", "SERVER3", "SERVER4" };
IList<Task> tasks = new List<Task>(); // collect all tasks in single collection
foreach( object i in arrObjects ) {
//if (ct.IsCancellationRequested) break;
//sb.Append(string.Format("{0}: {1}\r\n", HeavyOperation(i.ToString()),i));
//((IProgress<string>)progressReporter).Report(string.Format("Now Checking: {0}...", i));
tasks.Add(Task.Run(() => HeavyOperation(i.ToString())));
}
await Task.WhenAll(tasks).ConfigureAwait(false); // wait asynchronously for all tasks to complete
}
Now that PerformTaskAction is async, you also need to await on it.
Finally you invoke PerformTaskAction, making sure you also pass the CancellationToken.
await Task.Run( ()=> PerformTaskAction(cancelSource.Token), cancelSource.Token);

Is there a difference with Task.Run a void method and Task method returning null?

Consider a form with 2 buttons and a richtextbox:
public partial class MainForm : Form
{
CancellationTokenSource cts;
CancellationToken token;
public MainForm()
{
InitializeComponent();
}
private void MainForm_Load(object sender, EventArgs e)
{
cts = new CancellationTokenSource();
token = cts.Token;
var task = Task.Run(() => WriteSomeLines(), token);
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
cts.Dispose();
}
private void btnStart_Click(object sender, EventArgs e)
{
cts = new CancellationTokenSource();
token = cts.Token;
var task = Task.Run(() => WriteSomeLines(), token);
}
private void btnCancel_Click(object sender, EventArgs e)
{
try
{
cts.Cancel();
cts.Dispose();
}
catch (ObjectDisposedException exc)
{
MessageBox.Show(exc.GetType().Name);
//object disposed
}
}
public void WriteSomeLines()
{
if (ControlInvokeRequired(rtbLoops, () => rtbLoops.Text += "Starting new loop \r\n")) ;
else rtbLoops.Text += "Starting new loop \r\n";
for (int i = 0; i < 30; i++)
{
try
{
if (ControlInvokeRequired(rtbLoops, () => { rtbLoops.AppendText("New line " + i + "\r\n"); rtbLoops.ScrollToCaret(); })) ;
else rtbLoops.AppendText("New line " + i + "\r\n");
Thread.Sleep(250);
token.ThrowIfCancellationRequested();
}
catch (OperationCanceledException ae)
{
MessageBox.Show(ae.GetType().Name);
return;
}
}
return;
}
public bool ControlInvokeRequired(Control c, Action a)
{
if (c.InvokeRequired)
c.Invoke(new MethodInvoker(delegate { a(); }));
else
return false;
return true;
}
}
Is there a difference if WriteSomeLines() is returning void and I use return inside, or if WriteSomeLines() returns Task and I do return null there? I read that I cannot use await with void returning methods, but inserting
await task;
after task declaration (in the code above) compiles perfectly fine, and runs with no issues.
Edit:
private async void btnStart_Click(object sender, EventArgs e)
{
cts = new CancellationTokenSource();
token = cts.Token;
var task = Task.Run(() => WriteSomeLines(), token);
await task;
rtbLoops.Text += "Task complete";
}
This compiles with no issues if WriteSomeLines() returns void.
Also, slightly unrealted, am I disposing CancellationTokenSource correctly here?
Second Edit:
So is this the correct approach:
private async void btnStart_Click(object sender, EventArgs e)
{
cts.Dispose();
cts = new CancellationTokenSource();
token = cts.Token;
var task = Task.Run(() => WriteSomeLines(), token);
bool result = await task;
if(result == true) rtbLoops.Text += "Task complete \r\n";
}
and
public async Task<bool> WriteSomeLines()
{
if (ControlInvokeRequired(rtbLoops, () => rtbLoops.Text += "Starting new loop \r\n")) ;
else rtbLoops.Text += "Starting new loop \r\n";
for (int i = 0; i < 30; i++)
{
try
{
if (ControlInvokeRequired(rtbLoops, () => { rtbLoops.AppendText("New line " + i + "\r\n"); rtbLoops.ScrollToCaret(); })) ;
else rtbLoops.AppendText("New line " + i + "\r\n");
await Task.Delay(250);
token.ThrowIfCancellationRequested();
}
catch (OperationCanceledException ae)
{
MessageBox.Show(ae.GetType().Name);
return false;
}
}
return true;
You should never return a null task; that should cause a runtime NullReferenceException error.
You can use await within an async void method, but you cannot use await to consume an async void method (because you cannot await void).
I recommend that you review my async intro blog post; it should help you get a better understanding of async and await.
am I disposing CancellationTokenSource correctly here?
Your start button needs to cancel/dispose the old cts when it creates a new one.

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.

Categories

Resources