UWP MvvmLight Update UI after getting Messenger.default.Receive - c#

I am using MVVMLight in an UWP application, I have two screens: on MainScreen I have a button to open a second screen, and I also have another button to send some data from MainScreen to the second screen. I am using
Messenger.Default.Send(someobject)
and similarly
Messenger.Default.Register<Some>(this, (action) => ReceiveMsg(action));
I have to click the button on the main screen and send data to other view.
The problem is the data is not getting updated on the second screen, and causing an exception
The application called an interface that was marshalled for a different thread
I tried several ways to update UI, like this, this in fact I have tried all these possibilities like following
private async void ReceiveMsg(Some action)
{
try
{
//await Task.Factory.StartNew(() => {
// T1 = action.T1;
// RaisePropertyChanged(() => T1);
//});
//SharingData.UpdateScore(action);
//DispatcherHelper.Initialize();
//await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal
// , () =>
// {
// T1 = action.T1;
// RaisePropertyChanged(() => T1);
// });
//await Dispatcher.DispatchAsync(() =>
//{
// T1 = action.T1;
// RaisePropertyChanged(() => T1);
//},1000, Windows.UI.Core.CoreDispatcherPriority.Normal);
//await Window.Current.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
//{
// T1 = action.T1;
// RaisePropertyChanged(() => T1);
//});
//var views= Windows.ApplicationModel.Core.CoreApplication.Views.ToList()[0];
//await views.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => {
// T1 = action.T1;
// RaisePropertyChanged(() => T1);
//});
var thiswindow = Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher;
await thiswindow.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
team1 = action.Team1;
this.RaisePropertyChanged("Team1");
});
//DispatcherHelper.CheckBeginInvokeOnUI(
//() =>
//{
// T1 = action.T1;
// RaisePropertyChanged(() => T1);
//});
//DispatcherHelper.Reset();
//DispatcherHelper.Initialize();
}
catch (Exception ex)
{
//DispatcherHelper.Reset();
//Console
}
}
I have tried all the above segments one by one, but nothing works and still getting the "marshalled for a different thread" error.
Please tell me what I am doing wrong?
The property is getting update, like T1='Some Value' but not reflecting on UI and on RaisePropertyChanged its giving the exception.

I have to click the button on the main screen and send data to other view. The problem is the data is not getting updated on the second screen, and causing an exception
The application called an interface that was marshalled for a different thread.
The Send and Receive action of Messenger will be invoked in same thread. When you click the button in the MainScreen, the CoreWindow.Dispatcher is dispatcher of MainScreen. If you use this dispatcher to modify the content of second screen, it will throw exception.
To resolve it, you should invoke Send method in the main thread of second screen.
private async void Button_Click(object sender, RoutedEventArgs e)
{
newView = CoreApplication.CreateNewView();
int newViewId = 0;
await newView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
var id = Environment.CurrentManagedThreadId;
Frame frame = new Frame();
frame.Navigate(typeof(SecondPage), null);
Window.Current.Content = frame;
Window.Current.Activate();
newViewId = ApplicationView.GetForCurrentView().Id;
});
bool viewShown = await ApplicationViewSwitcher.TryShowAsStandaloneAsync(newViewId);
}
private async void SendBtn_Click(object sender, RoutedEventArgs e)
{
await newView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
var id = Environment.CurrentManagedThreadId;
Messenger.Default.Send("Test send message!!!");
});
}

Related

App is not loading the Views a second time if receiving a message from MessagingCenter and updating the UI afterwards

I am sending an intent I am receiving through a BroadcastReceiver to multiple Views in my shared-project. The DisplayResult-method implements the MessagingCenter.Send.
public class MyBroadcast : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
String action = intent.Action;
if (action.Equals(MainActivity.Instance.Resources.GetString(intentString)))
{
//Device.BeginInvokeOnMainThread(() => MainActivity.Instance.DisplayResult(intent));
//Task.Run(() => MainActivity.Instance.DisplayResult(intent));
//MainActivity.Instance.RunOnUiThread(() => MainActivity.Instance.DisplayResult(intent));
}
}
}
If a message is received I want to update my Views e.g. like this:
MessagingCenter.Subscribe<object, Model>(this, "HI", (sender, arg) =>
{
var dt = DateTime.Now;
_logger.Debug($"Task started: {dt}");
ActivityIndicator.IsVisible = true;
ActivityIndicator.IsRunning = true;
Task.Run(async () =>
{
await SomeTask();
}).GetAwaiter().GetResult();
_logger.Debug($"Task finished: {DateTime.Now - dt}");
ActivityIndicator.IsRunning = false;
ActivityIndicator.IsVisible = false;
}
});
The issue is if I use the approach with Task.Run(...) my Views are only showing up once but the ActivityIndicator is running. That means if I close a View and go back to the MainPage and navigate to another or the same View again it only shows a white screen.
If I use either Device.BeginInvokeOnMainThread(...) or RunOnUiThread(...) I get the skipped frames error. And my app is frozen until every Task within the MessagingCenter.Subscribe in my Views is finished. The ActivityIndicator is Not showing up.
You can have a try with using async method to invoke these method as follow:
MessagingCenter.Subscribe<PageMain, string>(this, "HI", async (sender, arg) =>
{
var dt = DateTime.Now;
_logger.Debug($"Task started: {dt}");
ActivityIndicator.IsVisible = true;
ActivityIndicator.IsRunning = true;
await SomeTask();
_logger.Debug($"Task finished: {DateTime.Now - dt}");
ActivityIndicator.IsVisible = false;
ActivityIndicator.IsRunning = false;
});

Send speech recognition args.Result as parameter in UWP desktop-bridge package

I'm trying to figure out, if it is possible send using Windows.Media.SpeechRecognition; args.Result.Text as parameter from UWP to Console application.
For example by following scenario I'm sending TextToSpeech(args.Result.Text); with args.Result.Text; value, where using Windows.Media.SpeechSynthesis; text-to-speech pronounces the recognition result each time when args.Result.Text; appears. textBlock2.Text = args.Result.Text; also displays result:
private async void ContinuousRecognitionSession_ResultGenerated(
SpeechContinuousRecognitionSession sender, SpeechContinuousRecognitionResultGeneratedEventArgs args)
{
await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
textBlock1.Text = args.Result.Text;
TextToSpeech(args.Result.Text);
});
}
but if I'm trying to send args.Result.Text; as parameter to console application, included with UWP in Desktop-Bridge package:
private async void ContinuousRecognitionSession_ResultGenerated(
SpeechContinuousRecognitionSession sender, SpeechContinuousRecognitionResultGeneratedEventArgs args)
{
await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
textBlock1.Text = args.Result.Text;
SendTTSResult(args.Result.Text);
});
}
to the requested function:
private async void SendTTSResult(string res)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
if (ApiInformation.IsApiContractPresent("Windows.ApplicationModel.FullTrustAppContract", 1, 0))
{
ApplicationData.Current.LocalSettings.Values["parameters"] = res;
await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync("Parameters");
}
});
}
Behavior of failure looks to me not really clear:
With first recognition result, it sends parameter to console application, which successfully loads, gets and displays this parameter. But with second request the problem backs away from this processing level, even if parameters sending function is unambiguously the cause of a failure SendTTSResult(args.Result.Text); function does not receives args.Result.Text but this happens already before function will comes in action, because preceding output display textBlock1.Text = args.Result.Text; also does not receives event anymore.
With async() => behavior is a bit different, it successfully receives event and sends value as parameter to console, in this case it happens 2-3 times from beginning of execution and voice requesting, then event disappears, when it is not even passed through SendTTSResult(string res), to imagine that the something in SendTTSResult(string res) does not allows pass string from recognition, but just stops, even if I put it in the end of TextToSpeech(string text) function, text to speech also stops receive event:
private async void SendTTSResult(string res)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async() =>
{
if (ApiInformation.IsApiContractPresent("Windows.ApplicationModel.FullTrustAppContract", 1, 0))
{
ApplicationData.Current.LocalSettings.Values["parameters"] = res;
await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync("Parameters");
}
});
}
It looks like sending of args.Result.Text value as parameter with SendTTSResult(string res) function works fine, sends string successfully, but at the same time presence of this function in ContinuousRecognitionSession_ResultGenerated somehow affects receiving of event inside of it. At the same time behavior of ContSpeechRecognizer_HypothesisGenerated looks completely different, args.Hypothesis.Text event appears each time and result successfully passes as parameter with same SendTTSResult(string res).
What can prevent an event from being executed when the function of sending a parameter is involved in its process, and how to fix it if possible?
Full code of continuous speech recognition added to my question on Windows Dev Center Send speech recognition args.Result as parameter in UWP desktop-bridge package
EDIT 1: **************************************************************************************************
Behind parameter function, Console Connector.exe only shows parameter without running of any app or anything else:
static void Main(string[] args)
{
string result = Assembly.GetExecutingAssembly().Location;
int index = result.LastIndexOf("\\");
string rootPath = $"{result.Substring(0, index)}\\..\\";
if (args.Length > 2)
{
switch (args[2])
{
case "/parameters":
string parameters = ApplicationData.Current.LocalSettings.Values["parameters"] as string;
Console.WriteLine("Parameter: " + parameters);
Console.ReadLine();
break;
}
}
}
Packeage.appxmanifest:
<uap:Extension Category="windows.appService">
<uap:AppService Name="SampleInteropService" />
</uap:Extension>
<desktop:Extension Category="windows.fullTrustProcess" Executable="Connector\Connector.exe">
<desktop:FullTrustProcess>
<desktop:ParameterGroup GroupId="Parameters" Parameters="/parameters" />
</desktop:FullTrustProcess>
</desktop:Extension>
EDIT 2: **************************************************************************************************
I've tried SendTTSResult(SpeechRecogVal); with change of variable value:
private async void ContinuousRecognitionSession_ResultGenerated(
SpeechContinuousRecognitionSession sender, SpeechContinuousRecognitionResultGeneratedEventArgs args)
{
await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
SpeechRecogVal = args.Result.Text;
});
}
but it is same behavior tbRec.Text = SpeechRecogVal; shows output successfully until I add SendTTSResult(SpeechRecogVal);,
private string _srVal;
public string SpeechRecogVal
{
get
{
return _srVal;
}
set
{
_srVal = value;
ValueChanged();
}
}
void ValueChanged()
{
tbRec.Text = SpeechRecogVal;
// SendTTSResult(SpeechRecogVal);
}
So, seems like problem is something between await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () => and if (ApiInformation.IsApiContractPresent("Windows.ApplicationModel.FullTrustAppContract", 1, 0)) and await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => of private async voidContinuousRecognitionSession_ResultGenerated(SpeechContinuousRecognitionSession sender, SpeechContinuousRecognitionResultGeneratedEventArgs args)
Also, I've tried:
private async void ContinuousRecognitionSession_ResultGenerated(
SpeechContinuousRecognitionSession sender, SpeechContinuousRecognitionResultGeneratedEventArgs args)
{
await SendTTSResult(args.Result.Text);
}
as Task:
async Task SendTTSResult(string res)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
if (ApiInformation.IsApiContractPresent("Windows.ApplicationModel.FullTrustAppContract", 1, 0))
{
ApplicationData.Current.LocalSettings.Values["parameters"] = res;
await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync("Parameters");
}
});
}
And it is also successful only with first request event instance response, then goes quite. So seems like ContinuousRecognitionSession_ResultGenerated is someway different from other options in Windows.Media.SpeechRecognition Namespace and not compatible with something in async Task SendTTSResult(string res) or rather with this code lines content:
ApplicationData.Current.LocalSettings.Values["parameters"] = args.Result.Text;
await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync("Parameters");
System.NullReferenceException occurs appservice disconnect scenario, Could you check the apservice's connection before send message?
For explain this, I create sample project that refer Stefanwick blog. And I also reproduce your issue when I does not invoke InitializeAppServiceConnection method in WPF client. If you want to send text to wpf, you could invoke Connection.SendMessageAsync method just like below SendMesssage click envent .
Capability
<Extensions>
<uap:Extension Category="windows.appService">
<uap:AppService Name="SampleInteropService" />
</uap:Extension>
<desktop:Extension Category="windows.fullTrustProcess" Executable="AlertWindow\AlertWindow.exe" />
</Extensions>
</Application>
</Applications>
<Capabilities>
<Capability Name="internetClient" />
<rescap:Capability Name="runFullTrust" />
</Capabilities>
WPF
private AppServiceConnection connection = null;
public MainWindow()
{
InitializeComponent();
InitializeAppServiceConnection();
}
private async void InitializeAppServiceConnection()
{
connection = new AppServiceConnection();
connection.AppServiceName = "SampleInteropService";
connection.PackageFamilyName = Package.Current.Id.FamilyName;
connection.RequestReceived += Connection_RequestReceived;
connection.ServiceClosed += Connection_ServiceClosed;
AppServiceConnectionStatus status = await connection.OpenAsync();
if (status != AppServiceConnectionStatus.Success)
{
MessageBox.Show(status.ToString());
this.IsEnabled = false;
}
}
private void Connection_ServiceClosed(AppServiceConnection sender, AppServiceClosedEventArgs args)
{
Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
{
Application.Current.Shutdown();
}));
}
private async void Connection_RequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
{
// retrive the reg key name from the ValueSet in the request
string key = args.Request.Message["KEY"] as string;
if (key.Length > 0)
{
Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
{
InfoBlock.Text = key;
}));
ValueSet response = new ValueSet();
response.Add("OK", "SEND SUCCESS");
await args.Request.SendResponseAsync(response);
}
else
{
ValueSet response = new ValueSet();
response.Add("ERROR", "INVALID REQUEST");
await args.Request.SendResponseAsync(response);
}
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
ValueSet response = new ValueSet();
response.Add("OK", "AlerWindow Message");
await connection.SendMessageAsync(response);
}
UWP
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (ApiInformation.IsApiContractPresent("Windows.ApplicationModel.FullTrustAppContract", 1, 0))
{
App.AppServiceConnected += MainPage_AppServiceConnected;
App.AppServiceDisconnected += MainPage_AppServiceDisconnected;
await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
}
}
private async void MainPage_AppServiceDisconnected(object sender, EventArgs e)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
Reconnect();
});
}
private void MainPage_AppServiceConnected(object sender, AppServiceTriggerDetails e)
{
App.Connection.RequestReceived += AppServiceConnection_RequestReceived;
}
private async void AppServiceConnection_RequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
{
string value = args.Request.Message["OK"] as string;
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
InfoBlock.Text = value;
});
}
private async void Reconnect()
{
if (App.IsForeground)
{
MessageDialog dlg = new MessageDialog("Connection to desktop process lost. Reconnect?");
UICommand yesCommand = new UICommand("Yes", async (r) =>
{
await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
});
dlg.Commands.Add(yesCommand);
UICommand noCommand = new UICommand("No", (r) => { });
dlg.Commands.Add(noCommand);
await dlg.ShowAsync();
}
}
private int count = 0;
private async void SendMesssage(object sender, RoutedEventArgs e)
{
count++;
ValueSet request = new ValueSet();
request.Add("KEY", $"Test{count}");
AppServiceResponse response = await App.Connection.SendMessageAsync(request);
// display the response key/value pairs
foreach (string value in response.Message.Values)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
StatusBlock.Text = value;
});
}
}
This is complete code sample.

Error when calling MainActivity method from another class

I have the following api call in my MainActivity.cs -
public async Task DoSomething()
{
var progressDialog = ShowLoading(_instance);
await Task.Factory.StartNew(() =>
{
var objectResponse = string.Empty;
string _apiUrl = "https://my-web-service-endpoint";
var request = (HttpWebRequest)WebRequest.Create(_apiUrl);
request.Method = "GET";
using (var response = _request.HttpWebResponse(request))
{
objectResponse = _request.HttpWebResponseBody(response);
}
progressDialog.Dismiss();
});
}
I am calling this method from a button click in SliderControl.cs -
public void loadTestView(View view)
{
Button btnDoSomething = view.FindViewById<Button>(Resource.Id.btnDoSomething);
if (btnDoSomething != null)
{
btnDoSomething .Click += async (sender, e) =>
{
await MainActivity.GetInstance().DoSomething();
};
}
}
However loadTestView is not called in the MainActivity.cs rather it is called when a used swipes onto a specific view in the application and handled in the SliderControl.cs , it is set like -
public override Java.Lang.Object InstantiateItem(ViewGroup container, int position)
{
var selectedView = Resource.Layout.Locator;
if (position == 0)
{
selectedView = Resource.Layout.Main;
}
if (position == 1)
{
selectedView = Resource.Layout.TestView;
}
View view = LayoutInflater.From(container.Context).Inflate(selectedView, container, false);
container.AddView(view);
if (position == 0)
{
loadMainView(view);
}
else if (position == 1)
{
loadTestView(view);
}
return view;
}
When I set a different buttons click event logic in the MainActivity.cs everything working accordingly, however when setting the button click login as it is above, I am met with the error -
Only the original thread that created a view hierarchy can touch its views.
The odd thing is the loader does actually come into view, however when I step into the await Task.Factory.StartNew(() => line the error hits.
I have tried changing the line to use Task.Run instead but same error, and also found some info on Device.BeginInvokeOnMainThread hwoever believe this is for Xamarin Forms. How can I get this to work?
You should use the UI Thread to update the UI / or any task that involve the UI:
Xamarin Forms Example:
Device.BeginInvokeOnMainThread (() => {
label.Text = "Async operation completed";
});
John wrote the great examples describing how to use Async/Await pattern. You may want to read more here https://johnthiriet.com/configure-await/#
Update: For Xamarin Native -
Problem:
new System.Threading.Thread(new System.Threading.ThreadStart(() => {
lblTest.Text = "updated in thread";
// Doesn't work because you can't modify UILabel on background thread!
})).Start();
Solution:
new System.Threading.Thread(new System.Threading.ThreadStart(() => {
InvokeOnMainThread (() => {
label1.Text = "updated in thread";
// this works! We are invoking in Main thread.
});
})).Start();

Network Activity Spinner Spins Forever

I'm building a Xamarin Forms app and have a problem with the network activity spinner in the Status Bar (iOS) continuing to spin forever. Below is the code I'm using to display a loading screen.
private async Task ExecuteConfigureCampaignCommand()
{
if (IsBusy)
{
return;
}
IsBusy = true;
//ConfigureCampaignCommand.ChangeCanExecute();
bool showAlert = false;
string campaign = null;
try
{
campaign = CrossSecureStorage.Current.GetValue(SettingNames.Campaign);
if (!String.IsNullOrEmpty(campaign))
{
StatusMessage = "Blah";
OnPropertyChanged("StatusMessage");
await Task.Delay(1000);
StatusMessage = "Blah";
OnPropertyChanged("StatusMessage");
await Task.Delay(700);
StatusMessage = "Blah";
OnPropertyChanged("StatusMessage");
await Task.Delay(500);
MessagingService.Current.SendMessage<string>(SettingNames.NavigationSales, campaign);
}
}
catch (Exception e)
{
var abc = e;
showAlert = true;
}
finally
{
IsBusy = false;
//ConfigureCampaignCommand.ChangeCanExecute();
}
if (showAlert)
{
await page.DisplayAlert("Uh Oh :(", "Unable to load campaign.", "OK");
}
}
This is called from a command:
public Command ConfigureCampaignCommand
{
get
{
return configureCampaignCommand ?? (configureCampaignCommand = new Command(async () => await ExecuteConfigureCampaignCommand(), () => { return !IsBusy; }));
}
}
Which is called from OnAppearing:
protected override void OnAppearing()
{
base.OnAppearing();
viewModel.ConfigureCampaignCommand.Execute(null);
}
The logic is fine - the View that I expect is loaded, however the network activity spinner continues spinning. If I add a 1 second delay:
Device.StartTimer(TimeSpan.FromMilliseconds(1), () =>
{
MessagingService.Current.SendMessage<string>(SettingNames.NavigationSales, campaign);
return false;
});
The spinner stops once the new view is loaded. If I remove the await Task.Delay's, the spinner stops once the view is loaded, however the loading messages are not visible to the user.
My question is, why does the network spinner continue?
Whenever you change IsBusy
OnPropertyChanged("IsBusy");
As you are calling this for your other properties but not for IsBusy I guess that this is probably the reason the change doesn't get picked up.

Progress bar in parallel loop invocation

I am trying to update a progressbar in a multithreaded environment. I know that a lot of questions already treat that question but none of the proposed solution have worked for me.
Here is the backbone of my code :
public static void DO_Computation(//parameters) {
//Intialisation of parameters
Parallel.For(struct initialisation with local data) {
//business logic
//Call to update_progressbar (located in an another class, as the DO_Computation function is in Computation.cs class (not deriving from Form).
WinForm.Invoke((Action)delegate {Update_Progress_Bar(i);}); //WinForm is a class that exposes the progressbar.
}
}
This is not working (the progressbar is freezing when arriving at 100%, which is normal (we can refer to the microsoft article in this matter (indeed, this is not a thread-safe operating method)).
The Microsoft site stiplates to wrap the Parallel.For loop into a Task routine as follows:
public static void DO_Computation(//parameters) {
//Intialisation of parameters
Task.Factory.StartNew(() =>
{
Parallel.For(struct initialosation with local data) {
//business logic
//Call to update_progressbar (ocated in an another class, as the DO_Computation function is in Computation.cs class (not deriving from Form).
WinForm.Invoke((Action)delegate {Update_Progress_Bar(i);}); //WinForm is a class that exposes the progressbar.
..
}
});
});
However this is not working as well, when debugging the thread is getting out of the Task scope directly.
EDIT 2:
Basically, my problem is divided in 3 parts: Computation.cs (where DO_Computation is exposed), WinForm which is the form containing the progress bar, and MainWindow which is the form that contains the button which when clicked opens the form with the progress bar.
I do not clearly understand what is the use of "Task" in this case.
Because it is going out of the Task scope without performing any Parallel.For work
Any ideas?
Many Thanks,
EDIT 3:
I upgraded my code with the help of Noseratio (thans a lot to him). However I have the same problem which is the code inside task is never executed. My code now looks like :
DoComputation method
//Some Initilasations here
Action enableUI = () =>
{
frmWinProg.SetProgressText("Grading Transaction...");
frmWinProg.ChangeVisibleIteration(true);
};
Action<Exception> handleError = (ex) =>
{
// error reporting
MessageBox.Show(ex.Message);
};
var cts = new CancellationTokenSource();
var token = cts.Token;
Action cancel_work = () =>
{
frmWinProg.CancelTransaction();
cts.Cancel();
};
var syncConext = SynchronizationContext.Current;
Action<int> progressReport = (i) =>
syncConext.Post(_ => frmWinProg.SetIteration(i,GrpModel2F.NumOfSim, true), null);
var task = Task.Factory.StartNew(() =>
{
ParallelLoopResult res = Parallel.For<LocalDataStruct>(1,NbSim, options,
() => new DataStruct(//Hold LocalData for each thread),
(iSim, loopState, DataStruct) =>
//Business Logic
if (token.IsCancellationRequested)
{
loopState.Stop();
}
progressReport(iSim);
//Business Logic
return DataStruct;
},
(DataStruct) =>
//Assiginig Results;
});//Parallel.For end
}, token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
task.ContinueWith(_ =>
{
try
{
task.Wait();
}
catch (Exception ex)
{
while (ex is AggregateException && ex.InnerException != null)
ex = ex.InnerException;
handleError(ex);
}
enableUI();
}, TaskScheduler.FromCurrentSynchronizationContext
());
Note that the Do_Computation function is itself called from a Form that runs a BackGroundWorker on it.
Use async/await, Progress<T> and observe cancellation with CancellationTokenSource.
A good read, related: "Async in 4.5: Enabling Progress and Cancellation in Async APIs".
If you need to target .NET 4.0 but develop with VS2012+ , you still can use async/await, Microsoft provides the Microsoft.Bcl.Async library for that.
I've put together a WinForms example illustrating all of the above. It also shows how to observe cancellation for Parallel.For loop, using ParallelLoopState.Stop():
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication_22487698
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
IEnumerable<int> _data = Enumerable.Range(1, 100);
Action _cancelWork;
private void DoWorkItem(
int[] data,
int item,
CancellationToken token,
IProgress<int> progressReport,
ParallelLoopState loopState)
{
// observe cancellation
if (token.IsCancellationRequested)
{
loopState.Stop();
return;
}
// simulate a work item
Thread.Sleep(500);
// update progress
progressReport.Report(item);
}
private async void startButton_Click(object sender, EventArgs e)
{
// update the UI
this.startButton.Enabled = false;
this.stopButton.Enabled = true;
try
{
// prepare to handle cancellation
var cts = new CancellationTokenSource();
var token = cts.Token;
this._cancelWork = () =>
{
this.stopButton.Enabled = false;
cts.Cancel();
};
var data = _data.ToArray();
var total = data.Length;
// prepare the progress updates
this.progressBar.Value = 0;
this.progressBar.Minimum = 0;
this.progressBar.Maximum = total;
var progressReport = new Progress<int>((i) =>
{
this.progressBar.Increment(1);
});
// offload Parallel.For from the UI thread
// as a long-running operation
await Task.Factory.StartNew(() =>
{
Parallel.For(0, total, (item, loopState) =>
DoWorkItem(data, item, token, progressReport, loopState));
// observe cancellation
token.ThrowIfCancellationRequested();
}, token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
// update the UI
this.startButton.Enabled = true;
this.stopButton.Enabled = false;
this._cancelWork = null;
}
private void stopButton_Click(object sender, EventArgs e)
{
if (this._cancelWork != null)
this._cancelWork();
}
}
}
Updated, here's how to do the same without async/await:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication_22487698
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
IEnumerable<int> _data = Enumerable.Range(1, 100);
Action _cancelWork;
private void DoWorkItem(
int[] data,
int item,
CancellationToken token,
Action<int> progressReport,
ParallelLoopState loopState)
{
// observe cancellation
if (token.IsCancellationRequested)
{
loopState.Stop();
return;
}
// simulate a work item
Thread.Sleep(500);
// update progress
progressReport(item);
}
private void startButton_Click(object sender, EventArgs e)
{
// update the UI
this.startButton.Enabled = false;
this.stopButton.Enabled = true;
Action enableUI = () =>
{
// update the UI
this.startButton.Enabled = true;
this.stopButton.Enabled = false;
this._cancelWork = null;
};
Action<Exception> handleError = (ex) =>
{
// error reporting
MessageBox.Show(ex.Message);
};
try
{
// prepare to handle cancellation
var cts = new CancellationTokenSource();
var token = cts.Token;
this._cancelWork = () =>
{
this.stopButton.Enabled = false;
cts.Cancel();
};
var data = _data.ToArray();
var total = data.Length;
// prepare the progress updates
this.progressBar.Value = 0;
this.progressBar.Minimum = 0;
this.progressBar.Maximum = total;
var syncConext = SynchronizationContext.Current;
Action<int> progressReport = (i) =>
syncConext.Post(_ => this.progressBar.Increment(1), null);
// offload Parallel.For from the UI thread
// as a long-running operation
var task = Task.Factory.StartNew(() =>
{
Parallel.For(0, total, (item, loopState) =>
DoWorkItem(data, item, token, progressReport, loopState));
// observe cancellation
token.ThrowIfCancellationRequested();
}, token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
task.ContinueWith(_ =>
{
try
{
task.Wait(); // rethrow any error
}
catch (Exception ex)
{
while (ex is AggregateException && ex.InnerException != null)
ex = ex.InnerException;
handleError(ex);
}
enableUI();
}, TaskScheduler.FromCurrentSynchronizationContext());
}
catch (Exception ex)
{
handleError(ex);
enableUI();
}
}
private void stopButton_Click(object sender, EventArgs e)
{
if (this._cancelWork != null)
this._cancelWork();
}
}
}

Categories

Resources