I'm learning about async/await keywords. I can't see what I'm doing wrote here syntactically?
I have the following two methods:
private async Task<string> PopupAsync()
{
String result;
using (StreamReader reader = File.OpenText(#"C:\temp\JBM_SchedulingModule.xap"))
{
Console.WriteLine("Opened file.");
txtData.Text = "Opened file.";
result = await reader.ReadToEndAsync();
}
return result;
}
and
private async void Button_Click(object sender, RoutedEventArgs e)
{
txtData.Text = await PopupAsync();
}
The main UI thread is freezing when I press the button and I don't want it to. Trying to understand why and how to fix.
Thanks!
Stephen is right. I figured it out. It does work as it's supposed to it. The reason why it was blocking was not because of ReadToEndAsync(), but the time it took to populate + load a 4MB string (from a file) to a WPF control.
Related
I realized an interface in WPF Page and I want to call ShowInputAsync in the MainWindow, which is where the Page is. In order to do that, I raise event in Page and block function with AutoResetEvent:
public AutoResetEvent OnMessageReceived;
public void MessageReceived(object sender, PageReturnMessageEventArgs e)
{
try
{
if (e.ToString() == "Cancel" || string.IsNullOrEmpty(e.ToString()))
{
throw new Exception("Exception of parsing of hours");
}
}
catch (Exception ex)
{
// log it
}
this.OnMessageReceived.Set();
}
private void Page_OnNextPageClick(object sender, EventArgs e)
{
// ...
MessageShow("Additional information", "How much time will this SuperProcess take?", "HOURS");
OnMessageReceived.WaitOne();
OnMessageReceived.Reset();
// ...
}
If I try to show ShowInputAsync from any function in MainWindow it works:
MetroDialogSettings s = new MetroDialogSettings();
s.AffirmativeButtonText = #"Create";
s.NegativeButtonText = #"Cancel";
s.AnimateShow = true;
var result = await this.ShowInputAsync("Test", "TestMessage", s);
if (result == null)
{ return; }
If I try to show this dialog from event handler, it doesn't work. After line
var result = await this.ShowInputAsync("Test", "TestMessage", s);
code returns to Page without any exception, executes line
OnMessageReceived.WaitOne();
and shows the window without any dialog, all Controls are showing and enabled, but I can't press them.
I also tried to put the dialog in a separate function, and call it with/without await, nothing changes.
To use AutoResetEvent after calling the Dialog to block further execution of code, also didn't help.
private AutoResetEvent OnMessageReturning;
var result = await this.ShowInputAsync(e.Title, e.Message, s);
OnMessageReturning.WaitOne();
Also tried to call the separate function in this way without result:
CancellationToken token;
TaskScheduler uiSched = TaskScheduler.FromCurrentSynchronizationContext();
await Task.Factory.StartNew(SeparateFunction, token, TaskCreationOptions.None, uiSched);
How can I call ShowInputAsync correctly in event handler? Or how can I call ShowInputAsync from Page in MainWindow?
P.S. There is TabControl in MainWindow, Page is placed in Frame of one TabItem
Using:
MahApps.Metro v1.4.1 (NuGet package)
Windows OS 7
Visual Studio Express 2015
.NET Framework 4.5
UPD: Easy sample is https://github.com/awg21/MahAppsShowInputAsyncFromPage
I found a solution here I'm using:
TryFindParent<> is an extension method defined in MahApps.Metro.Controls.TreeHelper, and ShowMessageAsync<> is defined in MahApps.Metro.Controls.Dialogs.DialogManager
I'm new in TPL world, and I did that code:
var myItems = myWpfDataGrid.SelectedItems;
this.Dispatcher.BeginInvoke(new Action(() =>
{
var scheduler = new LimitedConcurrencyLevelTaskScheduler(5);
TaskFactory factory = new TaskFactory(scheduler);
foreach (MyItem item in myItems)
{
Task myTask = factory.StartNew(() =>
DoLoooongWork(item)
).ContinueWith((t) =>
{
Debug.WriteLine(t.Exception.Message);
if (t.Exception.InnerException != null)
{
Debug.WriteLine(t.Exception.InnerException.Message);
}
},
TaskContinuationOptions.OnlyOnFaulted);
}
}), null);
The only one access to gui is "var myItems = myWpfDataGrid.SelectedItems;"
and it is read only! The function "DoLoooongWork()" does access to serial ports, etc. It's a separated SDK function that doesn't access the GUI. I know that "Dispatcher.BeginInvoke" is a bit redundant, but I don't know what I can do, or what I'm doing wrong. The only reason to this code is to free the GUI while "DoLoooongWork()" executes, but the GUI is frozen!
What's wrong with that code?
edit
Thanks to #Euphoric help, I discovered the problem that is similar to that post:
COM Interop hang freezes entire COM system. How to cancel COM call
I presume some objects inside DoLoooongWork require thread affinity and message pumping. Try my ThreadWithAffinityContext and see if helps, use it like this:
private async void Button_Click(object sender, EventArgs e)
{
try
{
using (var staThread = new Noseratio.ThreadAffinity.ThreadWithAffinityContext(
staThread: true, pumpMessages: true))
{
foreach (MyItem item in myItems)
{
await staThread.Run(() =>
{
DoLoooongWork(item);
}, CancellationToken.None);
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
More info about ThreadWithAffinityContext.
[UPDATE] You mentioned in the comments that the code inside DoLoooongWork looks like this:
zkemkeeper.CZKEM axCZKEM1 = new zkemkeeper.CZKEM();
axCZKEM1.Connect_Net(ip, port);
I never heard of "zkemkeeper" before, but I did a brief search and found this question. Apparently, Connect_Net only establishes the connection and starts a session, while the whole communication logic happens asynchronously via some events, as that question suggests:
bIsConnected = axCZKEM1.Connect_Net("192.168.0.77", Convert.ToInt32("4370"));
if (bIsConnected == true)
{
iMachineNumber = 1;
if (axCZKEM1.RegEvent(iMachineNumber, 65535))
{
this.axCZKEM1.OnFinger += new kemkeeper._IZKEMEvents_OnFingerEventHandler(axCZKEM1_OnFinger);
this.axCZKEM1.OnVerify += new zkemkeeper._IZKEMEvents_OnVerifyEventHandler(axCZKEM1_OnVerify);
// ...
}
}
That would be a whole different story. Leave a comment if that's the case and you're still interested in some solution.
I had a hunch that something working with serial port would try to use application's event loop to do it's work. So it actually bypasses the whole dispatcher and thread system and blocks the application. I'm not experienced in this field so I don't know how to solve it, but this is different question.
I have a query for getting a list of prescriptions as below:
var PRSCRPTSQuery = GV.dbContext.Load(GV.dbContext.GetPRSCRPTQuery(GV.curCustomer.CustCode,
oOrdritemEdited.ProdCode, oOrdritemEdited.MedCode));
PRSCRPTSQuery.Completed += new EventHandler(PRSCRPTSQuery_Completed);
In the query completed event, I have the following code :
void PRSCRPTSQuery_Completed(object sender, EventArgs e)
{
lstPRSCRPT = GV.dbContext.PRSCRPTs.Where(p=>p.Status =="Activated").ToList();
if (lstPRSCRPT.Count > 0)
{
foreach (var rec in lstPRSCRPT)
{
var OrderItemQuery = GV.dbContext.Load(GV.dbContext.GetOrdritemsQuery(rec.PresNo));
OrderItemQuery.Completed += new EventHandler(OrderItemQuery_Completed);
}
}
}
The list lstPRSCRPT can contain more than one record. I presume, the foreach loop will advance to the next item in the loop without waiting for the OrderItemQuery_Completed event which is below:
void OrderItemQuery_Completed(object sender, EventArgs e)
{
lstOrderItem = GV.dbContext.OrderItems.ToList();
if (lstOrderItem.Count > 0)
{
foreach (var OrdrItemRec in lstOrderItem)
{
TotTonnes = (double)(TotTonnes + OrdrItemRec.Quantity);
}
}
}
Is there any work around for this situation? I am new to the asynchronous type of programming in SL
I see where your coming from and when i first started Silverlight programming i gripped to my preconceptions of synchronous execution so i know what i have when ive finished calling a query and also i know exactly where it's errored.
Silverlight however takes this concept and tries to rip it from you yelling "This way is better trust me!" and for the purpose it serves of enriching client side interactivity it certainly succeeds. It just takes time. You just need to learn more about the style of how to link it all together.
The link previously shown by Faster Solutions shows where C# is going in terms of asynchronous coding but it pays to know what its actually accomplishing for you. Some of which you've already grasped in the code you've linked in the question.
When i've faced the same situation you have where you have back to back async callbacks is to raise an event when i've finished doing what i'm doing. For example:
public event EventHandler<EventArgs> LoadComplete;
public int QueryCount {get;set;}
public int QuerysCompleted {get;set;}
public void GetItems()
{
var PRSCRPTSQuery = GV.dbContext.Load(GV.dbContext.GetPRSCRPTQuery
(GV.curCustomer.CustCode, oOrdritemEdited.ProdCode, oOrdritemEdited.MedCode));
PRSCRPTSQuery.Completed += new EventHandler(PRSCRPTSQuery_Completed);
LoadComplete += loader_LoadComplete;
}
void PRSCRPTSQuery_Completed(object sender, EventArgs e)
{
lstPRSCRPT = GV.dbContext.PRSCRPTs.Where(p=>p.Status =="Activated").ToList();
if (lstPRSCRPT.Count > 0)
{
QueryCount = lstPRSCRPT.Count;
foreach (var rec in lstPRSCRPT)
{
var OrderItemQuery = GV.dbContext.Load(GV.dbContext.GetOrdritemsQuery(rec.PresNo));
OrderItemQuery.Completed += new EventHandler(OrderItemQuery_Completed);
}
}
}
void OrderItemQuery_Completed(object sender, EventArgs e)
{
QueryCompleted++;
lstOrderItem = GV.dbContext.OrderItems.ToList();
if (lstOrderItem.Count > 0)
{
foreach (var OrdrItemRec in lstOrderItem)
{
TotTonnes = (double)(TotTonnes + OrdrItemRec.Quantity);
}
}
if(QueryCompleted == QueryCount)
{
RaiseLoadComplete();
}
}
public void RaiseLoadComplete()
{
if(LoadComplete != null)
{
LoadComplete(this, new EventArgs());
}
}
void loader_LoadComplete(object sender, EventArgs e)
{
//Code to execute here
}
I attach an event when starting the first query of what code to execute when i'm done. In the first query call back i initialise a count of how many responses i am expecting. Then in the second query callback i increment until i get the right amount and call the event to say im done.
The only caution with this approach is if one of the queries error, The final code will never get executed.
You might find that the VS Async CTP of interest. It introduces the new "async" keyword for handling asyncronous events. He's a blog explaining it: VS Async
So I've downloaded the samples from http://archive.msdn.microsoft.com/ManagedMediaHelpers.
I've got my code working using MP3MediaStreamSource. However, I don't fully understand the code would like some explanation.
public partial class MainPage : PhoneApplicationPage
{
private static string mediaFileLocation = "http://file-here.mp3";
private static HttpWebRequest request = null;
private static Mp3MediaStreamSource mss = null;
public MainPage()
{
InitializeComponent();
}
private void RequestCallback(IAsyncResult asyncResult)
{
HttpWebResponse response = request.EndGetResponse(asyncResult) as HttpWebResponse;
Stream s = response.GetResponseStream();
mss = new Mp3MediaStreamSource(s, response.ContentLength);
Deployment.Current.Dispatcher.BeginInvoke(
() =>
{
this.wp7AudioElement.Volume = 100;
this.wp7AudioElement.SetSource(mss);
});
}
private void Button_Click(object sender, RoutedEventArgs e)
{
request = WebRequest.CreateHttp(MainPage.mediaFileLocation);
// NOTICE
// Makes this demo code easier but I wouldn't do this on a live phone as it will cause the whole
// file to download into memory at once.
//
// Instead, use the asynchronous methods and read the stream in the backgound and dispatch its
// data as needed to the ReportGetSampleCompleted call on the UIThread.
request.AllowReadStreamBuffering = true;
IAsyncResult result = request.BeginGetResponse(new AsyncCallback(this.RequestCallback), null);
}
}
It's really just the last method I need explained, I don't understand the Notice as to why it's a bad idea and how to do it differently?
Basically, it is trying to tell you that you are downloading 1 file COMPLETELY before it plays. It is not a good idea, since if the file is 10 MB, it may take a while before it completely downloads.
A better idea would be to chunk the file using Encoders, and read it in on need basis.
Currently, I have something like:
public partial class Form1 : Form
{
delegate void StringDelegate(string value);
private FTP m_ftp;
public Form1()
{
InitializeComponent();
}
private void connect_Click(object sender, EventArgs e)
{
OnResponse("Connecting");
m_ftp = new FTP(server.Text);
m_ftp.ResponseReceived += new FTPResponseHandler(m_ftp_ResponseReceived);
m_ftp.Connected += new FTPConnectedHandler(m_ftp_Connected);
m_ftp.BeginConnect(user.Text, password.Text);
}
void m_ftp_Connected(FTP source)
{
// when this happens we're ready to send command
OnResponse("Connected.");
}
void m_ftp_ResponseReceived(FTP source, FTPResponse Response)
{
OnResponse(Response.Text);
}
private void OnResponse(string response)
{
if (this.InvokeRequired)
{
this.Invoke(new StringDelegate(OnResponse), new object[] { response } );
return;
}
}
private void getFileList_Click(object sender, EventArgs e)
{
FTPFiles files = m_ftp.EnumFiles();
fileList.Items.Clear();
foreach (FTPFile file in files)
{
fileList.Items.Add( new ListViewItem( new string[] { file.Name, file.Size.ToString() } ));
}
tabs.SelectedIndex = 1;
}
private void upload_Click(object sender, EventArgs e)
{
FileStream stream = File.OpenRead("\\My Documents\\My Pictures\\Waterfall.jpg");
m_ftp.SendFile(stream, "waterfall.jpg");
stream.Close();
}
Which works fine - this example was taken from the samples. However, after a recent re-visit I have a question. In this particular case since OnResponse() function doesn't update the UI, it seems to serve no purpose here. I removed it (as well as all the calls to it) and it still works like before. Am I missing something?
After reading up more about multi threading with forms, I came to understand that this mechanism (demonstrated in the code above) is there to make sure the UI is responsive.
So in case when we need to say, update a UI element (such as textbox, label etc) we would have OnResponse implemented as follows:
delegate void StringDelegate(string dummy);
void OnResponse(string dummy)
{
if(!InvokeRequired)
{
button1.Text = dummy;
}
else
Invoke(new StringDelegate(OnResponse),new object[] {enabled});
}
If this function is implemented as:
delegate void StringDelegate(string dummy);
void OnResponse(string dummy)
{
if(InvokeRequired)
{
Invoke(new StringDelegate(OnResponse),new object[] {dummy});
return;
}
}
What's the use to have it at all? Is it absolutely necessary?
And another question: is ftp object running on its own thread here?
The FTP object is definitely running on its own thread. How do I know? This line:
m_ftp.BeginConnect(user.Text, password.Text);
This is an asynchronous method. Once you call this, the FTP component will use a thread from the .NET threadpool to do all of the work. This dedicated thread is the one that is used to "raise" the events. Ultimately a "raised event" is just one or more method calls to all of the delegates added to the event invocation list; it is this dedicated thread spun up by the Begin method that calls these methods. This thread is not the same thread as the thread that runs the UI, hence the need for the Invoke calls.
If you want the FTP component to use the UI thread, you'd use the Connect method instead of the BeginConnect method. This means your events wont work either, nor will your UI respond to interaction - this is completely expected because a thread can only do one thing at a time: it's either servicing the UI, or executing the FTP code. This is why you need a 2nd thread.
Make sense?
-Oisin