I am lost in this one, i want my Viewmodel to use a event delegate so i can subscribe to it, open some dialog and wait for the dialog result. Later the ViewModel should do whatever it wants with the dialog result.
Here is how i implemented it (resumed code):
public class MyViewModel()
{
public delegate TributaryDocument SearchDocumentEventHandler();
public event SearchDocumentEventHandler SearchDocument;
//Command for the search button
public CommandRelay SearchDocumentCommand { get; set; }
//Document that i found in the dialog.
public TributaryDocument Document { get; set; }
public MyViewModel()
{
SearchDocumentCommand = new CommandRelay(DoSearchDocument);
}
//The command execution
public void DoSearchDocument()
{
//Event used here !
Document = SearchDocument?.Invoke();
}
}
public class MyUIControl : UserControl
{
public MainWindow MainWindow { get; }
public MyUIControl()
{
MainWindow = Application.Current.Windows[0] as MainWindow;
DataContextChanged += MyUIControl_DataContextChanged;
}
private void MyUIControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var modelView = (MyViewModel)DataContext;
modelView.SearchDocument += MyUIControl_SearchDocument;
}
private TributaryDocument MyUIControl_SearchDocument()
{
//Dont know what to do here... i am lost on this part.
return await MainWindow.ShowDialog(new MyDocumentSearcherDialog());
}
}
//The signature for MainWindow.ShowDialog
public async Task<object> ShowDialog(object dialog)
{
return await DialogHost.Show(dialog, "MainDialog");
}
MyDocumentSearcherDialog is just a dialog where i search and return a TributaryDocument object.
The problem to my understanding comes from this part (since i cant compile it):
private TributaryDocument MyUIControl_SearchDocument()
{
return await MainWindow.ShowDialog(new MyDocumentSearcherDialog());
}
I cant use await without changing the method signature to async. If i change it to async then i must return a Task<TributaryDocument> and change the event delegate:
public delegate Task<TributaryDocument> SearchDocumentEventHandler();
//On MyUIControl
private Task<TributaryDocument> MyUIControl_SearchDocument()
{
return await MainWindow.ShowDialog(new MyDocumentSearcherDialog());
}
//On MyViewModel
public async void DoSearchDocument()
{
//Event used here !
Document = await Task.Run(async () => await SearchDocument?.Invoke());
}
If i do this i get the following exception:
Additional information: The calling thread must be STA, because many
UI components require this.
It seems like all you need to do is to remove the Task.Run (there is no need to Offload to another thread in this situation). The Task.Run will definitely give you a STA Thread Exception if you are doing UI work from within.
However, in short the Async and Await Pattern will create a continuation with the current SynchronisationContext, so there is no need to worry about it.
public async void DoSearchDocument()
{
await SearchDocument?.Invoke();
}
Note : Since this is an event, it's about the only place it's OK to use async void.
Related
I have a class which plays some music like this. It also saves the GUI thread id in a private int during construction:
public class MediaPlayer {
public event EventHandler<Track> OnTrackComplete;
private int GuiThreadId;
public MediaPlayer(...){
...
this.GuiThreadId = Thread.CurrentThread.ManagedThreadId;
}
public void Play(){
Task t = Task.Factory.StartNew(() =>
{
//On Song complete
FireOnTrackComplete();
});
}
protected virtual void FireOnTrackComplete()
{
if (OnTrackComplete != null)
OnTrackComplete(this, loadedTrack);
}
}
Is it possible to call FireOnTrackComplete() on a Thread with a specific ID. In this case, the ID is stored in the this.GuiThreadId?
Most of the solutions I have come across suggest I use invokes in my GUI code in methods which listen to the OnTrackComplete event handler. I want to avoid doing this. I want to do everything in the MediaPlayer class
Based on the accepted answer bellow this is how I changed my code
public class MediaPlayer {
public event EventHandler<Track> OnTrackComplete;
private SynchronizationContext callerCtx;
public MediaPlayer(...){
...
callerCtx = System.Threading.SynchronizationContext.Current;
}
public void Play(){
Task t = Task.Factory.StartNew(() =>
{
//On Song complete
FireOnTrackComplete();
});
}
protected virtual void FireOnTrackComplete()
{
Action e = () =>
{
if (OnTrackComplete != null)
OnTrackComplete(this, loadedTrack);
};
FireEvent(e);
}
//... Other events ... //
protected virtual void FireEvent(Action e)
{
if (callerCtx == null)
e();
else
callerCtx.Post(new SendOrPostCallback((_) => e()), null);
}
}
The SynchronizationContext class was meant to solve this problem. Copy the value of its Current property in the constructor, use its Post() or Send() method later. This ensures your library will work with any GUI class library. Like this:
class MediaPlayer {
public MediaPlayer() {
callersCtx = System.Threading.SynchronizationContext.Current;
//...
}
private void FireOnTrackComplete() {
if (callersCtx == null) FireOnTrackCompleteImpl();
else callersCtx.Post(new System.Threading.SendOrPostCallback((_) => FireOnTrackCompleteImpl()), null);
}
protected virtual void FireOnTrackCompleteImpl() {
var handler = OnTrackComplete;
if (handler != null) handler(this, loadedTrack);
}
private System.Threading.SynchronizationContext callersCtx;
}
Pass a reference to the main dispatcher (=GUI-Thread's dispatcher) and call Invoke on it directly with your callback code.
public class MediaPlayer {
public event EventHandler<Track> OnTrackComplete;
private Dispatcher { get; set; }
public MediaPlayer(Dispatcher guiDispatcher){
// Other code ...
if(guiDispatcher == null)
throw new ArgumentNullException("guiDispatcher", "Cannot properly initialize media player, since no callback can be fired on GUI thread.");
Dispatcher = guiDispatcher;
}
public void Play() {
// Fire immediately on thread calling 'Play', since we'll forward exec. on gui thread anyway.
FireOnTrackComplete();
}
protected virtual void FireOnTrackComplete()
{
// Pretending "loadedTrack" was set somewhere before.
Dispatcher.Invoke(() => {
if (OnTrackComplete != null)
OnTrackComplete(this, loadedTrack);
});
}
}
// Somewhere in your initialization code
// ...
MediaPlayer player = new MediaPlayer(App.Current.Dispatcher); // If you use WPF. Don't know if this applies to WinForms too.
// ...
To be able to execute code on another thread, you must have a queue or message pump waiting for a new item to process.
This is already done in winforms and wpf via Control.Invoke and IDispatcher.Invoke. If you really want to avoid having the Control perform the listening, you'll have to pass the control into the MediaPlayer. It's really awkward, but there's a big complaint on SO that the first answer is "how about you stop doing that thing you're trying to do".. so here goes:
public class MediaPlayer {
public event EventHandler<Track> OnTrackComplete;
private int GuiThreadId;
private readonly Control control;
public MediaPlayer(..., Control control){
...
this.GuiThreadId = Thread.CurrentThread.ManagedThreadId;
this.contrl = control;
}
public void Play(){
Task t = Task.Factory.StartNew(() =>
{
//On Song complete
FireOnTrackComplete();
});
}
protected virtual void FireOnTrackComplete()
{
var trackComplete = OnTrackComplete;
if (onTrackComplete != null)
this.control.Invoke((MethodInvoker) delegate {trackComplete(this, loadedTrack);});
}
}
Apologies if there's a typo, I don't have everything in front of me to verify with; but this should get you what you're after.
I'm currently working on a .net 5 Blazor application.
I use events to pass data from one component to another.
Unfortunately my current logic is synchronous - but I would rather use an asynchronous event handler.
Thus, I need to use the following code to handle my event:
Task.Run(async () => await ChangeNumbers());
Is there a possibility to handle events asynchronously without Task.Run?
My State service looks like this:
public class MyComponentState
{
public int MyNumber { get; set; }
// Is there a way to declare this event Action async??
public event Action OnChange;
public void DoStuff(int myNumber)
{
MyNumber = myNumber;
NotifyStateChanged();
}
private void NotifyStateChanged() => OnChange?.Invoke();
}
The component to handle the state looks like this:
public class MyOtherComponentDisplay : ComponentBase, IDisposable
{
[Inject]
public MyComponentState MyComponentState { get; set; }
protected override void OnInitialized()
{
// this should all be handled async... i could use OnInitializedAsync
MyComponentState.OnChange += OnChangeHandler;
}
private void OnChangeHandler()
{
// Is there a way to handle/declare this without Task.Run(async ...) - but async right away??
Task.Run(async () => await ChangeNumbers());
}
private async Task ChangeNumbers()
{
// Some operations with MyComponentState.MyNumber async needed!!!
StateHasChanged();
}
public void Dispose()
{
MyComponentState.OnChange -= OnChangeHandler;
}
}
Is there a way to declare and handle events async?
Do you know how to solve this problem?
The basic adoptation would be an async void handler:
private async void OnChangeHandler()
{
// Is there a way to handle/declare this without Task.Run(async ...)
// - but async right away??
// Task.Run(async () => await ChangeNumbers());
await ChangeNumbers();
await InvokeAsync(StateHasChanged); // probably needed
}
The way you're doing things looks strange to me. That's not how I do events in Blazor. (Maybe you're coming from Web Forms?)
Generally, a custom event is defined like:
MyControl.razor
[Parameter]
public EventCallback<SomeType> EventName{ get; set; }
#code {
someMethod (){
EventName.InvokeAsync(SomeType data);
}
}
And the handler in the consuming control can be async if you want:
MyPage.razor
<MyControl EventName=OnChangeHandler />
#code {
private async Task OnChangeHandler()
{
await ChangeNumbers();
}
}
I am trying to implement a long-running background process, that periodically reports on its progress, to update the UI in a UWP app. How can I accomplish this? I have seen several helpful topics, but none have all of the pieces, and I have been unable to put them all together.
For example, consider a user who picks a very large file, and the app is reading in and/or operating on the data in the file. The user clicks a button, which populates a list stored on the page with data from the file the user picks.
PART 1
The page and button's click event handler look something like this:
public sealed partial class MyPage : Page
{
public List<DataRecord> DataRecords { get; set; }
private DateTime LastUpdate;
public MyPage()
{
this.InitializeComponent();
this.DataRecords = new List<DataRecord>();
this.LastUpdate = DateTime.Now;
// Subscribe to the event handler for updates.
MyStorageWrapper.MyEvent += this.UpdateUI;
}
private async void LoadButton_Click(object sender, RoutedEventArgs e)
{
StorageFile pickedFile = // … obtained from FileOpenPicker.
if (pickedFile != null)
{
this.DataRecords = await MyStorageWrapper.GetDataAsync(pickedFile);
}
}
private void UpdateUI(long lineCount)
{
// This time check prevents the UI from updating so frequently
// that it becomes unresponsive as a result.
DateTime now = DateTime.Now;
if ((now - this.LastUpdate).Milliseconds > 3000)
{
// This updates a textblock to display the count, but could also
// update a progress bar or progress ring in here.
this.MessageTextBlock.Text = "Count: " + lineCount;
this.LastUpdate = now;
}
}
}
Inside of the MyStorageWrapper class:
public static class MyStorageWrapper
{
public delegate void MyEventHandler(long lineCount);
public static event MyEventHandler MyEvent;
private static void RaiseMyEvent(long lineCount)
{
// Ensure that something is listening to the event.
if (MyStorageWrapper.MyEvent!= null)
{
// Call the listening event handlers.
MyStorageWrapper.MyEvent(lineCount);
}
}
public static async Task<List<DataRecord>> GetDataAsync(StorageFile file)
{
List<DataRecord> recordsList = new List<DataRecord>();
using (Stream stream = await file.OpenStreamForReadAsync())
{
using (StreamReader reader = new StreamReader(stream))
{
while (!reader.EndOfStream)
{
string line = reader.ReadLine();
// Does its parsing here, and constructs a single DataRecord …
recordsList.Add(dataRecord);
// Raises an event.
MyStorageWrapper.RaiseMyEvent(recordsList.Count);
}
}
}
return recordsList;
}
}
The code for the time check I got from following this.
As written, this code makes the app unresponsive with a large file (I tested on a text file on the order of about 8.5 million lines). I thought adding async and await to the GetDataAsync() call would prevent this? Does this not do its work on a thread aside from the UI thread? Through Debug mode in Visual Studio, I have verified the program is progressing as expected... it is just tying up the UI thread, making the app unresponsive (see this page from Microsoft about the UI thread and asynchronous programming).
PART 2
I have successfully implemented before an asynchronous, long-running process that runs on a separate thread AND still updates the UI periodically... but this solution does not allow for the return value - specifically the line from PART 1 that says:
this.DataRecords = await MyStorageWrapper.GetDataAsync(pickedFile);
My previous, successful implementation follows (most of the bodies cut out for brevity). Is there a way to adapt this to allow for return values?
In a Page class:
public sealed partial class MyPage : Page
{
public Generator MyGenerator { get; set; }
public MyPage()
{
this.InitializeComponent();
this.MyGenerator = new Generator();
}
private void StartButton_Click(object sender, RoutedEventArgs e)
{
this.MyGenerator.ProgressUpdate += async (s, f) => await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, delegate ()
{
// Updates UI elements on the page from here.
}
this.MyGenerator.Start();
}
private void StopButton_Click(object sender, RoutedEventArgs e)
{
this.MyGenerator.Stop();
}
}
And in the Generator class:
public class Generator
{
private CancellationTokenSource cancellationTokenSource;
public event EventHandler<GeneratorStatus> ProgressUpdate;
public Generator()
{
this.cancellationTokenSource = new CancellationTokenSource();
}
public void Start()
{
Task task = Task.Run(() =>
{
while(true)
{
// Throw an Operation Cancelled exception if the task is cancelled.
this.cancellationTokenSource.Token.ThrowIfCancellationRequested();
// Does stuff here.
// Finally raise the event (assume that 'args' is the correct args and datatypes).
this.ProgressUpdate.Raise(this, new GeneratorStatus(args));
}
}, this.cancellationTokenSource.Token);
}
public void Stop()
{
this.cancellationTokenSource.Cancel();
}
}
Finally, there are two supporting classes for the ProgressUpdate event:
public class GeneratorStatus : EventArgs
{
// This class can contain a handful of properties; only one shown.
public int number { get; private set; }
public GeneratorStatus(int n)
{
this.number = n;
}
}
static class EventExtensions
{
public static void Raise(this EventHandler<GeneratorStatus> theEvent, object sender, GeneratorStatus args)
{
theEvent?.Invoke(sender, args);
}
}
It is key to understand that async/await does not directly say the awaited code will run on a different thread. When you do await GetDataAsync(pickedFile); the execution enters the GetDataAsync method still on the UI thread and continues there until await file.OpenStreamForReadAsync() is reached - and this is the only operation that will actually run asynchronously on a different thread (as file.OpenStreamForReadAsync is actually implemented this way).
However, once OpenStreamForReadAsync is completed (which will be really quick), await makes sure the execution returns to the same thread it started on - which means UI thread. So the actual expensive part of your code (reading the file in while) runs on UI thread.
You could marginally improve this by using reader.ReadLineAsync, but still, you will be returning to UI thread after each await.
ConfigureAwait(false)
The first trick you want to introduce to resolve this problem is ConfigureAwait(false).
Calling this on an asynchronous call tells the runtime that the execution does not have to return to the thread that originally called the asynchronous method - hence this can avoid returning execution to the UI thread. Great place to put it in your case is OpenStreamForReadAsync and ReadLineAsync calls:
public static async Task<List<DataRecord>> GetDataAsync(StorageFile file)
{
List<DataRecord> recordsList = new List<DataRecord>();
using (Stream stream = await file.OpenStreamForReadAsync().ConfigureAwait(false))
{
using (StreamReader reader = new StreamReader(stream))
{
while (!reader.EndOfStream)
{
string line = await reader.ReadLineAsync().ConfigureAwait(false);
// Does its parsing here, and constructs a single DataRecord …
recordsList.Add(dataRecord);
// Raises an event.
MyStorageWrapper.RaiseMyEvent(recordsList.Count);
}
}
}
return recordsList;
}
Dispatcher
Now you freed up your UI thread, but introduced yet another problem with the progress reporting. Because now MyStorageWrapper.RaiseMyEvent(recordsList.Count) runs on a different thread, you cannot update the UI in the UpdateUI method directly, as accessing UI elements from non-UI thread throws synchronization exception. Instead, you must use UI thread Dispatcher to make sure the code runs on the right thread.
In the constructor get reference to the UI thread Dispatcher:
private CoreDispatcher _dispatcher;
public MyPage()
{
this.InitializeComponent();
_dispatcher = Window.Current.Dispatcher;
...
}
Reason to do it ahead is that Window.Current is again accessible only from the UI thread, but the page constructor definitely runs there, so it is the ideal place to use.
Now rewrite UpdateUI as follows
private async void UpdateUI(long lineCount)
{
await _dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
// This time check prevents the UI from updating so frequently
// that it becomes unresponsive as a result.
DateTime now = DateTime.Now;
if ((now - this.LastUpdate).Milliseconds > 3000)
{
// This updates a textblock to display the count, but could also
// update a progress bar or progress ring in here.
this.MessageTextBlock.Text = "Count: " + lineCount;
this.LastUpdate = now;
}
});
}
I am trying to consume an interface but I am having some difficulty here.
I am trying to set it to a xamrin list view in behind a content page
public class xxxApiClient : IApi
{
readonly string url = "http://localhost:81/ ";
readonly IHttpService httpService;
public xxxApiClient(IHttpService httpService)
{
this.httpService = httpService;
}
public Task<List<JobsList>> GetJobs() => httpService.Get<List<JobsList>>($"{url}JobsLists");
}
How ever I am not to sure how I cosume getjobs correclty I am trying the following
public partial class JobsPage : ContentPage ,xxxWC.Interface.IApi
{
public xxxWC.Interface.IApi api = new ful;
public JobsPage ()
{
InitializeComponent ();
}
private Task SetItemSource()
. {
. JobListing.ItemsSource = FuelAp
}
How do I use the get jobs correctly above in the method setItemSource?.
The bit I am having hard time to understand is here.
How do I call the base GetJobs method I have already created in API Client.
Task<List<JobsList>> IApi.GetJobs()
{
throw new NotImplementedException();
}
private Task SetItemSource()
{
JobListings.ItemsSource =await GetJobs();
}
}
Edit 2
Ok based on suggestions below I updated My Code as such
IHttpService httpService;
xxxApiClient _api = newxxxApiClient(httpService);
public JobsPage ()
{
InitializeComponent ();
}
private Task SetItemSource()
{
JobListings.ItemsSource =await GetJobs();
}
But i get the below error
Severity Code Description Project File Line Suppression State
Error CS0236 A field initializer cannot reference the non-static
field, method, or property
'JobsPage.httpService' xxxCallManagmentAppMobile C:\Work\xxxCallAppDev\XamForms\xxxCallManagmentApp\xxxCallManagmentAppMobile\FuelCallManagmentAppMobile\Views\JobsPage.xaml.cs 17 Active
Can someone explain why
Edit 3
Ok i got a bit further but still having some issues. as the main method is not awaited how do I call set SetItemSource.
xxxApiClient _api ;
public JobsPage ()
{
InitializeComponent ()
SetItemSource();
}
private async Task SetItemSource()
{
JobListings.ItemsSource = await client.GetJobs();
}
Assuming that IApi has been mapped to xxxApiClient implementation
Try resolving the service using the DependencyService so that it is available to be used in the view
public partial class JobsPage : ContentPage {
public readonly IApi client;
public JobsPage () {
InitializeComponent ();
client = DependencyService.Get<IApi>();
}
private async Task SetItemSource() {
JobListing.ItemsSource = await client.GetJobs();
//...
}
}
As for calling the SetItemSource, it is async so should be awaited. That can't be done in the constructor.
Consider creating a event that can be raised and its handler used to await the desired behavior.
private event EventHandler loadingData = delegate { };
private async void onLoadingData(object sender, Eventargs args) {
JobListing.ItemsSource = await client.GetJobs();
}
Full code
public partial class JobsPage : ContentPage {
public readonly IApi client;
public JobsPage () {
InitializeComponent ();
//resolving client
client = DependencyService.Get<IApi>();
//subscribing to event
loadingData += onLoadingData;
//raising event
loadingData(this, EventArgs.Empty);
}
private async Task SetItemSource() {
JobListing.ItemsSource = await client.GetJobs();
//...
}
private event EventHandler loadingData = delegate { };
private async void onLoadingData(object sender, Eventargs args) {
JobListing.ItemsSource = await client.GetJobs();
}
}
Although a custom event was created, you could just as easily used on of the event/eventhandler of the view.
All of that code should actually live inside of a view model and then bound to the view in a binding context.
I have a window displaying service, with a CloseWindow method that is called by the view. I want to create a blocking method in my calling code. So I can block while a window pops up and to allow outputs to come back from the window.
Is this use of Manual Reset acceptable? Are there any technical or design problems with it or with the way I mix it with TPL?
Here is that service
private readonly ManualResetEvent closedEvent = new ManualResetEvent(true);
public void DisplayWindow(){
window = new MyWindow();
}
public void CloseWindow() {
window.Close();
closedEvent.Set();
}
//new
public async Task WaitClosed()
{
await Task.Run(() => this.closedEvent.WaitOne());
}
here is some code that calls it.
public void DisplayWindow(string content, string title)
{
dialogservice.DisplayWindow();
}
public async Task DisplayWindowAsync(string content, string title)
{
dialogservice.DisplayWindow();
await dialogservice.WaitClosed();
}
It looks like it could be done more simply and without the hung thread:
private readonly TaskCompletionSource<bool> windowClosed
= new TaskCompletionSource<bool>();
public Task WindowClosed { get { return windowClosed.Task; } }
public void CloseWindow() {
window.Close();
windowClosed.TrySetResult(true);
}
with:
await dialogservice.WindowClosed;