Using async and await that updates data in real time - wpf

I have tried the code mentioned in this answer: https://stackoverflow.com/a/27089652/
It works fine and I want to use it for running a PowerShell script in for loop. GUI was freezing initially then I tried the code mentioned in this answer: https://stackoverflow.com/a/35735760/
Now GUI does not freeze while the PowerShell script is running in the background although nothing is updated in the textbox until for loop is complete. I want to see the results updating in real time. Here is the code I am running:
private async void run_click(object sender, RoutedEventArgs e)
{
Text1.Text = "";
await Task.Run(() => PS_Execution(Text1));
}
internal async Task PS_Execution(TextBox text)
{
PowerShell ps = PowerShell.Create();
ps.AddScript(script.ToString());
{
Collection<PSObject> results = ps.Invoke();
foreach (PSObject r in results)
{
text.Dispatcher.Invoke(() =>
{
text.Text += r.ToString();
});
await Task.Delay(100);
}
}
}
Maybe I am missing something important. Please help me understand how to solve this problem.

Instead of using ps.Invoke() which is synchronous call and will wait for all results to return use ps.BeginInvoke() instead. Then subscribe to the DataAdded event of the output PSDataCollection and use the action to update your ui.
private async void run_click(object sender, RoutedEventArgs e)
{
Text1.Text = "";
await Task.Run(() => PS_Execution(Text1));
}
internal async Task PS_Execution(TextBox text)
{
using PowerShell ps = PowerShell.Create();
ps.AddScript(script.ToString());
PSDataCollection<string> input = null;
PSDataCollection<string> output = new();
IAsyncResult asyncResult = ps.BeginInvoke(input, output);
output.DataAdded += (sender, args) =>
{
var data = sender as PSDataCollection<string>;
text.Dispatcher.Invoke(() =>
{
text.Text += data[args.Index];
});
};
}

Related

OnAfterRender or OnInitializedAsync function to refresh data?

I'd like to refresh my data each minute. for this, I use a timer.
`
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
//Configuration des graphiques
Alert.Info("OnInitializedAsync");
timer = new System.Threading.Timer(async (object? stateInfo) =>
{
loading = true;
GetDataAPI();
}, new System.Threading.AutoResetEvent(false), 2000, 2000);
}
`
this work fine, but when I load the page for the fisrt time, It spend a long time before to load data. when I delete the Time it's very faster.
so my question, is it in the OnInitializedAsync that I use the timer ? I've read a lot of documention on the cycle but don't really see the difference between OnAfterRender or OnInitializedAsync.
should I load data the first time in OnAfterRender with FirstRender ? and then the timer in OnInitializedAsync ?
thanks for your help.
You can break out the timer into a separate class with an event to drive updates:
public class MyTimer
{
private System.Timers.Timer _timer = new System.Timers.Timer();
public event EventHandler<ElapsedEventArgs>? TimerElapsed;
public MyTimer(double period)
=> SetTimer(period);
private void SetTimer(double period)
{
_timer = new System.Timers.Timer(period);
_timer.Elapsed += OnTimedEvent;
_timer.AutoReset = true;
_timer.Enabled = true;
}
private void OnTimedEvent(object? source, ElapsedEventArgs e)
=> this.TimerElapsed?.Invoke(this, e);
}
And then you can use it like this. I've added a simple message that is updated every 5 seconds to demo getting new data. Note there's no delay on the initial load.
#page "/"
#implements IDisposable
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<div class="alert alert-info">
#message
</div>
#code {
private MyTimer? timer;
private string message = DateTime.Now.ToLongTimeString();
private bool isGettingData;
protected async override Task OnInitializedAsync()
{
await this.GetDataAsync();
// Set for every 5 seconds
timer = new(5000);
timer.TimerElapsed += this.OnTimeElapsed;
}
private async void OnTimeElapsed(object? sender, EventArgs e)
{
await this.GetDataAsync();
// Update the UI
await this.InvokeAsync(StateHasChanged);
}
private async ValueTask GetDataAsync()
{
// Only get the data again if we finished the last get
if (isGettingData)
return;
isGettingData = true;
// emulate async fetching data
await Task.Delay(100);
message = DateTime.Now.ToLongTimeString();
isGettingData = false;
}
public void Dispose()
{
if (timer is not null)
timer.TimerElapsed -= this.OnTimeElapsed;
timer = null;
}
}

Due to the use of Dispatcher,t he window still locks

I have a program similar to chatbot in Wpf.
I have a stack where I create the user controls I have and enter them.
I have to use Net3.5 .
The response from the server is delayed.
The problem I have is when I type and send the textbox the server does not answer,
I can not type another question and the window is locked.
Did I use Dispatcher correctly?
private void send_Click(object sender, RoutedEventArgs e)
{
stack.Children.Add(new UserControl_Send()
{
DataSend = txt_input.Text,
DateTimeBot = DateTime.Now.ToString("HH:mm")
});
DispatchFit();
}
private void DispatchFit()
{
Dispatcher.BeginInvoke(new Action(ResponsServer), DispatcherPriority.Background);
}
public void ResponsServer()
{
Thread.Sleep(3000);
stack.Children.Add(new UserControl_Receive()
{
DataRecive = get(txt_input.Text),
DateTimeBot = DateTime.Now.ToString("HH:mm"),
});
}
When that ResponsServer() callback is being processed on your UI thread, then that Sleep is elongating the amount of time that callback is taking to process (Sleep does not pump the UI's dispatcher message queue).
If you want your callback to be done after 3 seconds, then you need to use a timer, or you can use "async" to cause a delayed processing of your callback.
Look at this question: Delayed Dispatch Invoke?
Or use this to have a BackgroundWorker do the delay and then call your ResponsServer on the UI thread (not the best code as it creates a new BackgroundWorker each time).
https://www.codeproject.com/Tips/240274/Execute-later-for-delayed-action
You are a little confused about the methods.
If I understand correctly, then Sleep is an emulation of the delay in the execution of sending a message to the server.
Then you need something like:
private async void send_ClickAsync(object sender, RoutedEventArgs e)
{
stack.Children.Add(new UserControl_Send()
{
DataSend = txt_input.Text,
DateTimeBot = DateTime.Now.ToString("HH:mm")
});
await ResponsServerAsync();
stack.Children.Add(new UserControl_Receive()
{
DataRecive = get(txt_input.Text),
DateTimeBot = DateTime.Now.ToString("HH:mm"),
});
}
public async Task ResponsServerAsync()
{
Thread.Sleep(3000);
}
For .Net Framework 3.5
private void send_Click(object sender, RoutedEventArgs e)
{
stack.Children.Add(new UserControl_Send()
{
DataSend = txt_input.Text,
DateTimeBot = DateTime.Now.ToString("HH:mm")
});
Thread thread = new Thread(ResponsServer);
thread.Start();
}
public void ResponsServer()
{
Thread.Sleep(3000);
if (Dispatcher.CheckAccess())
{
StackChildrenAdd();
}
else
{
Dispatcher.InvokeAsync(StackChildrenAdd);
}
}
private void StackChildrenAdd()
{
stack.Children.Add(new UserControl_Receive()
{
DataRecive = get(txt_input.Text),
DateTimeBot = DateTime.Now.ToString("HH:mm"),
});
}

Redirect the output of kestrel's console to richtextbox

well i have an api running on Kestrel server locally,i needed to run it without showing the console so i used the vbs answer from superuser and it worked, now my problem is that i want to stream out the messages that were shown on console to somewhere else like a file or richtextbox of windowsform ,if there is any why ?
thank you
You can redirect the Kestrel's output to a local file, then you can watch the file changes.
Example to redirect the Kestrel output:
dotnet run > d:\kestrel-output.txt
Here is a code snip to watch a text file's changes. This approach uses time interval but you can try the FileSystemWatcher.
...
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
var cancellationTokenSource = new CancellationTokenSource();
Load += (s, e) =>
{
const string targetFile = #"d:\kestrel-output.txt";
_ = WatchTextFileAsync(targetFile,
TimeSpan.FromSeconds(0.5),
change =>
{
Invoke(new Action(() =>
{
richTextBox1.AppendText(change);
richTextBox1.ScrollToCaret();
})
);
},
cancellationTokenSource.Token
);
};
Closing += (s, e) =>
{
if (!e.Cancel)
cancellationTokenSource.Cancel();
};
}
public async Task WatchTextFileAsync(
string path,
TimeSpan checkInterval,
Action<string> onChange,
CancellationToken token = default)
{
var fs = File.Open(path, FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite);
fs.Seek(fs.Length, SeekOrigin.Begin); //ignore current content
var sr = new StreamReader(fs);
try
{
while (!token.IsCancellationRequested)
{
if (fs.Position > fs.Length) //new file? replaced? if so read from the beginning
{
fs.Seek(0, SeekOrigin.Begin);
}
if (fs.Position < fs.Length) //check offset
{
var change = await sr.ReadToEndAsync();
onChange?.Invoke(change);
}
await Task.Delay(checkInterval, token);
}
}
finally
{
sr.Dispose();
fs.Dispose();
}
}
}
...

Windows Phone 8 - Getting Multiple Street Names

I created for Windows Phone 8.0 Silverlight App an async method GetStreetName
string streetname;
private async Task<string> GetStreetName(int i)
{
MapAddress address;
ReverseGeocodeQuery query = new ReverseGeocodeQuery();
query.GeoCoordinate = Route[i].Item1;
query.QueryCompleted += (s, e) =>
{
if (e.Error != null)
return;
address = e.Result[0].Information.Address;
streetname = address.Street;
};
query.QueryAsync();
return streetname;
}
and I call it using the await operator inside of a for loop:
for (int i = 0; i < Route.Count; i++)
{
ListBox.Items.Add(await GetStreetName(i));
}
but I always get only the street name of the first loaded geoposition back and I have no idea why (I thought the await operator is waiting until the async method is finished).
Additional info: i just saw that this is not 100% clear at this short snippet, streetname and Route are global "variables", Route is a tuple list where the first item is a geocoordinate.
How can I fix this issue?
You are returning from GetStreetName before the results are ready becayse query.QueryAsync(); just starts the query and doesn't wait for it to be complete.
On top of that, you're writing all results to the same global streetname.
You need to use a TaskCompletionSource.
Try something like this:
private async Task<string> GetStreetNameAsync(int i)
{
var tcs = new TaskCompletionSource<IEnumerable<string>>();
EventHandler<QueryCompletedEventArgs<IList<MapLocation>>> handler = (s, e) =>
{
if (e.Error != null)
{
tcs.TrySetException(e.Error);
return;
}
if (e.Cancelled)
{
tcs.TrySetCanceled();
return;
}
tcs.TrySetResult(e.Result[0].Information.Address.Street);
};
var query = new ReverseGeocodeQuery();
query.GeoCoordinate = Route[i].Item1;
try
{
query.QueryCompleted += handler;
query.QueryAsync();
return await tcs.Task;
}
finally
{
query.QueryCompleted -= handler;
}
}

Cross thread operation not valid in BackgroundWorker

I want to display some data on form load in a data gridview , the data which i want to display is in large number of rows , when i use background worker processor it show me the following error.
My code:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
try
{
FTPUtility obj = new FTPUtility();
dataGridViewRequest.DataSource = obj.ListRequestFiles();
dataGridViewRequest.Columns[0].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridViewRequest.Columns[1].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridViewRequest.Columns[2].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridViewResponses.DataSource = obj.ListResponseFiles();
dataGridViewResponses.Columns[0].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridViewResponses.Columns[1].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridViewResponses.Columns[2].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
Form load:
private void FormFTP_Load(object sender, EventArgs e)
{
try
{
//this.comboBoxRequests.SelectedIndex = 0;
backgroundWorker1.RunWorkerAsync();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
There are many different ways to prevent the form from being freezed.
For example you can load your data like this:
private async void Form_Load(object sender, EventArgs e)
{
//do some initializations
await LoadData();
//do some other initializations that you need to perform.
}
private async Task LoadData()
{
//Load your data here
//For example
FTPUtility obj = new FTPUtility();
dataGridViewRequest.DataSource = obj.ListRequestFiles();
}
This way when running the form, the commands run in the sequence you wrote while the UI is responsive and you will not face with common difficulties of using BackgroundWorker or threads like Cross thread operation exceptions.
The key point is in using async/await. For more information read Asynchronous Programming with Async and Await
Remember that this way, every where you want to call LoadData, you should call it this way:
await LoadData();
And the method that you write this code in, should be async:
private async void RefreshButton_Click(object sender, EventArgs e)
{
await LoadData();
}
EDIT For .Net 4.0
For .Net 4.0 you can use both Task.Run or BackgroundWorker. I recommend Task.Run because it is more simple and more readable.
Please note Cross Thread Operation Exception will throw when you access the UI elements from another thread than UI. In this situations you should use this.Invoke(new Action(()=>{/*Access UI Here*/})); instead. And never put a time-consuming task in your invoke part.
BackgroundWorker Approach:
private void Form1_Load(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
//If you put some code here for example MessageBox.Show("");
//The code will immadiately run and don't wait for worker to complete the task.
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
this.LoadData();
}
public void LoadData()
{
var ftp = new FtpUtility();
var data = ftp.ListRequestFiles();
this.Invoke(new Action(() =>
{
//Setup columns and other UI stuff
//Set datasource of grid
this.dataGridView1.DataSource = data;
}));
}
Remember everywhere you formerly used LoadData, now you should use backgroundWorker1.RunWorkerAsync(); instead.
If you want to do a job after the worker completes the task, put your job in backgroundWorker1_RunWorkerCompleted or in Invoke part of LoadData.
Task.Run Approach
private void Form1_Load(object sender, EventArgs e)
{
Task.Run(() =>
{
LoadData();
})
.ContinueWith(x =>
{
//You can put codes you want run after LoadData completed here
//If you access the UI here, you should use Invoke
});
//If you put some code here for example MessageBox.Show("");
//The code will immadiately run and don't wait for worker to complete the task.
}
public void LoadData()
{
var ftp = new FtpUtility();
var data = ftp.ListRequestFiles();
this.Invoke(new Action(() =>
{
//Setup columns and other UI stuff
//Set datasource of grid
this.dataGridView1.DataSource = data;
}));
}
Remember everywhere you formerly used LoadData, now you should use Task.Run(()=>{LoadData();}); instead.
If you want to do a job after the LoadData completes, put your job in ContinueWith or in Invoke part of LoadData.
Try this.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
try
{
dataGridViewRequest.Invoke(new Action(() => {
FTPUtility obj = new FTPUtility();
dataGridViewRequest.DataSource = obj.ListRequestFiles();
dataGridViewRequest.Columns[0].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridViewRequest.Columns[1].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridViewRequest.Columns[2].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridViewResponses.DataSource = obj.ListResponseFiles();
dataGridViewResponses.Columns[0].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridViewResponses.Columns[1].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridViewResponses.Columns[2].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
}));
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void FormFTP_Load(object sender, EventArgs e)
{
try
{
//this.comboBoxRequests.SelectedIndex = 0;
backgroundWorker1.RunWorkerAsync();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}

Resources