I am developing a Xamarin.Forms application, where i am showing a label that updates its text on each second after starting a timer. The label's text value is bound to a ViewModel-property named "TimerValue". Its used to display the recording duration of an audio input. The timer method looks like the following:
/// <summary>
/// The start recorder.
/// </summary>
private void StartTimer()
{
var stopwatch = new Stopwatch();
stopwatch.Start();
Device.StartTimer(
new TimeSpan(0, 0, 0, 1, 0),
() =>
{
if (this.isRecording)
{
this.TimerValue = stopwatch.Elapsed.Seconds;
return true;
}
stopwatch.Stop();
this.TimerValue = 0;
return false;
});
}
The timer is started within a Task.Run that is called on a UI-Event (GestureRecognizer.Tapped):
await Task.Run(() => this.StartTimer()).ConfigureAwait(false);
This code works, but why does it? Task.Run starts a new Thread on the ThreadPool, and the TimerValue sits on the UI-Thread. Shouldn't I have to call Xamarin.Forms.Device.BeginInvokeOnMainThread to update the UI in this case?
Why does the UI get updated if I set a UI-Bound Property within a Task.Run?
Related
I’ve run into a performance obstacle and I’m uncertain of the cause, all of this is running under VS2022 & Net6.0. As this is my 1st time using this combination of a modal windows form, and progress bar, with the work running on a background thread and two Progress objects updating the UI, the progress bar, and a text label, I don’t know where to attack the problem. Prior to placing the workload on a background thread, everything was snappy, searching a thousand files with about 600 lines of text in each, in about a minute. Naturally, the windows form was frozen during this, which is why the workload was placed on a background thread.
After doing so, the workload will be 25-50% complete before the UI starts displaying the values from the Progress objects, and overall, the entire process now takes 10x as long to complete. Progress objects aren’t skipping over any values sent to them, the UI thread just seems slow in getting the information. Likewise, if I try to drag the modal form to a new spot on the desktop it’s unresponsive for 20—30 seconds before it finally moves. One more thing, I can step through the code on the background thread and see it calling the Progress updaters, but the UI thread is just very slow in responding to them.
I could use some suggestions on how to uncover the problem or if clearly evident, point out where the likely problem could be. Here are the essential controls and methods used.
public class SearchProgressForm : Form
{
private System.Windows.Forms.Button btnSearch = new Button();
private System.Windows.Forms.TextBox txtTextSearch = new TextBox();
private System.Windows.Forms.Label lblSearchFile = new Label();
private System.Windows.Forms.ProgressBar SearchProgressBar = new ProgressBar();
public event LogSearchEventHandler SearchSucceededEvent;
protected void OnSearchSucceeded(LogSearchEventArguments p_eventArguments)
{
LogSearchEventHandler handler = SearchSucceededEvent;
if (handler != null)
{
handler(this, p_eventArguments);
}
}
private void InitializeComponent()
{
this.btnSearch.Name = "btnSearch";
this.btnSearch.Text = "Search";
this.btnSearch.Click += new System.EventHandler(this.btnSearch_Click);
this.lblSearchFile.Text = "Searching File: ";
this.txtTextSearch.Text = "search string";
}
public SearchProgressForm() { }
private void btnSearch_Click(object sender, EventArgs e)
{
this.SearchByText(this.txtTextSearch.Text);
}
private void SearchByText(string p_searchParameter)
{
// Setup a progress report for thr ProgressBar
var _progressBarUpdate = new Progress<int>(value =>
{
this.SearchProgressBar.Value = value;
this.SearchProgressBar.Refresh();
});
var _progressFileNameUpdate = new Progress<string>(value =>
{
this.lblSearchFile.Text = "Searching File For : " + value;
this.lblSearchFile.Refresh();
});
// Start search on a backgroud thread and report progress as it occurs
Task.Run(async () => await this.SearchByStringAsync(p_searchParameter, _progressBarUpdate, _progressFileNameUpdate));
}
private async Task SearchByStringAsync(string p_searchParameter, IProgress<int> p_progressBar, IProgress<string> p_progressFileName)
{
await Task.Delay(1);
TextFileReader textFileReader = new TextFileReader();
LogSearchEventArguments logSearchEventArguments = null;
long _sessionloopCount = 0;
long _totalTextLinesCount = this.GetTotalSearchCount(p_searchParameter, SearchType.TextString);
// Get file names from SQL table
var _logFiles = DataOperations.LogFileSortableList(null);
foreach (var log in _logFiles)
{
// Format a file name to be read from the file system
string _fileName = log.Directory + "\\" + log.FileName;
p_progressFileName.Report(log.FileName);
// If we've raised an event for this file, then stop iterating over remaning text
if (logSearchEventArguments != null)
{
logSearchEventArguments = null;
break;
}
// Read in file contents from file system
List<string> _fileContents = textFileReader.ReadAndReturnStringList(_fileName);
long _fileTotalRecordCount = _fileContents.Count;
long _fileRecordCount = 0;
foreach (var _line in _fileContents)
{
if (_line.ToUpper().Contains(p_searchParameter.ToUpper()))
{
// Raise an event so search parameter and file name can be captured in another form
logSearchEventArguments =
new LogSearchEventArguments
(
"TextSearch", p_searchParameter, SearchType.TextString, true, log,
new DateTime(
Convert.ToInt32("20" + log.FileName.Substring(14, 2)),
Convert.ToInt32(log.FileName.Substring(16, 2)),
Convert.ToInt32(log.FileName.Substring(18, 2)))
);
// We found a match, so no further searching is needed in this log file,
// and it's been flagged in the DB, so raise the event to save search parameter and file name
// then break out of this loop to get the next file to search in.
this.OnSearchSucceeded(logSearchEventArguments);
break;
}
// These calcs are based on actual searches performed
_fileRecordCount++;
_sessionloopCount++;
p_progressBar.Report(Convert.ToInt32((_sessionloopCount * 100) / _totalTextLinesCount));
}
// Because we exit a search as soon as the 1st match is made, need to resynch all counts
// and update the progress bar accordingly
if (_fileRecordCount < _fileTotalRecordCount)
{
long _countDifference = _fileTotalRecordCount - _fileRecordCount;
// Add count difference to sessionLoopCount and update progress bar
_sessionloopCount += _countDifference;
p_progressBar.Report(Convert.ToInt32((_sessionloopCount * 100) / _totalTextLinesCount));
}
}
//Search is complete set Progress to 100% and report before exiting
p_progressBar.Report(100);
// Close the modal SearchForm and exit
this.Close();
}
}
I solved this problem but I'm still not certain of what caused it. I eliminated the method "private void SearchByText(string p_searchParameter)" and moved the code there into the btnSearch_Click event handler so I could call my background worker "SearchByStringAsync" directly from the button click event handler.
I also updated the EFCore NuGet Packages, which were version Net6.0 to version 6.0.4, because of single line of code in my Async background method, "var _logFiles = DataOperations.LogFileSortableList(null)".
That call returned a Sortable BindingList, using BindingList <T>. Between the NuGet updates and a minor change on a custom comparer method in my BindingList <T> class, the windows modal form now updates the ProgressBar and Label text as expected, and the form now responds immediately to user interaction.
I am trying to build a simple app using Microsoft Azure's Cognitive Services Speech To Text SDK in Unity3D. I've following this tutorial, and it worked quite well. The only problem with this tutorial is that the Speech-To-Text is activated by a button. When you press the button, it'll transcribe for the duration of a sentence, and you'll have to press the button again for it to transcribe again. My problem is I'd like it to start transcribing as soon as the program is run in Unity, rather than having to press a button each time I want to transcribe a sentence.
Here is the code.
public async void ButtonClick()
{
// Creates an instance of a speech config with specified subscription key and service region.
// Replace with your own subscription key and service region (e.g., "westus").
var config = SpeechConfig.FromSubscription("[My API Key]", "westus");
// Make sure to dispose the recognizer after use!
using (var recognizer = new SpeechRecognizer(config))
{
lock (threadLocker)
{
waitingForReco = true;
}
// Starts speech recognition, and returns after a single utterance is recognized. The end of a
// single utterance is determined by listening for silence at the end or until a maximum of 15
// seconds of audio is processed. The task returns the recognition text as result.
// Note: Since RecognizeOnceAsync() returns only a single utterance, it is suitable only for single
// shot recognition like command or query.
// For long-running multi-utterance recognition, use StartContinuousRecognitionAsync() instead.
var result = await recognizer.RecognizeOnceAsync().ConfigureAwait(false);
// Checks result.
string newMessage = string.Empty;
if (result.Reason == ResultReason.RecognizedSpeech)
{
newMessage = result.Text;
}
else if (result.Reason == ResultReason.NoMatch)
{
newMessage = "NOMATCH: Speech could not be recognized.";
}
else if (result.Reason == ResultReason.Canceled)
{
var cancellation = CancellationDetails.FromResult(result);
newMessage = $"CANCELED: Reason={cancellation.Reason} ErrorDetails={cancellation.ErrorDetails}";
}
lock (threadLocker)
{
message = newMessage;
waitingForReco = false;
}
}
}
void Start()
{
if (outputText == null)
{
UnityEngine.Debug.LogError("outputText property is null! Assign a UI Text element to it.");
}
else if (startRecoButton == null)
{
message = "startRecoButton property is null! Assign a UI Button to it.";
UnityEngine.Debug.LogError(message);
}
else
{
// Continue with normal initialization, Text and Button objects are present.
}
}
void Update()
{
lock (threadLocker)
{
if (startRecoButton != null)
{
startRecoButton.interactable = !waitingForReco && micPermissionGranted;
}
}
}
I've tried removing the Button object, but then the speech-to-text won't run.
Any tips or advice would be amazing. Thank you.
Per the comments in the script of the tutorial your referenced:
// Starts speech recognition, and returns after a single utterance is recognized. The end of a
// single utterance is determined by listening for silence at the end or until a maximum of 15
// seconds of audio is processed. The task returns the recognition text as result.
// Note: Since RecognizeOnceAsync() returns only a single utterance, it is suitable only for single
// shot recognition like command or query.
// For long-running multi-utterance recognition, use StartContinuousRecognitionAsync() instead.
But it's not as simple as replacing 'RecognizeOnceAsync' with 'StartContinuousRecognitionAsync', because the behaviours are different. RecognizeOnceAsync will basically turn on your mic for a maximum of 15 seconds, and then stop listening.
Instead, make the button into 'should I listen continuously or not?' using StartContinuousRecognitionAsync and StopContinuousRecognitionAsync, and then change your Start function to simply start up a new recognizer and have it waiting for the Speech Recognizer event to come through. Below is the script I used to enable this functionality:
using UnityEngine;
using UnityEngine.UI;
using Microsoft.CognitiveServices.Speech;
public class HelloWorld : MonoBehaviour
{
public Text outputText;
public Button startRecordButton;
// PULLED OUT OF BUTTON CLICK
SpeechRecognizer recognizer;
SpeechConfig config;
private object threadLocker = new object();
private bool speechStarted = false; //checking to see if you've started listening for speech
private string message;
private bool micPermissionGranted = false;
private void RecognizingHandler(object sender, SpeechRecognitionEventArgs e)
{
lock (threadLocker)
{
message = e.Result.Text;
}
}
public async void ButtonClick()
{
if (speechStarted)
{
await recognizer.StopContinuousRecognitionAsync().ConfigureAwait(false); // this stops the listening when you click the button, if it's already on
lock(threadLocker)
{
speechStarted = false;
}
}
else
{
await recognizer.StartContinuousRecognitionAsync().ConfigureAwait(false); // this will start the listening when you click the button, if it's already off
lock (threadLocker)
{
speechStarted = true;
}
}
}
void Start()
{
startRecordButton.onClick.AddListener(ButtonClick);
config = SpeechConfig.FromSubscription("KEY", "REGION");
recognizer = new SpeechRecognizer(config);
recognizer.Recognizing += RecognizingHandler;
}
void Update()
{
lock (threadLocker)
{
if (outputText != null)
{
outputText.text = message;
}
}
}
}
And below is a gif of me using this functionality. You'll not that I don't click the button at all (and it was only clicked once, prior to the gif being recorded)(also, sorry for the strange sentences, my coworkers kept interrupting asking who I was talking to)
Code in the main form:
private delegate bool IncreaseProbarHandler(int nIncVal); //Declare a delegate to increase the progress bar value.
private IncreaseProbarHandler _IncHanler = null;
private List<Microsoft.Win32.RegistryKey> _RKeys = new List<Microsoft.Win32.RegistryKey>(); //Store the RegistryKey.
public MainForm() {
InitializeComponent();
}
private void MainForm_Load(object sender, EventArgs e) {
new Thread(ProThread).Start();
RecursiveRegedit(Microsoft.Win32.Registry.CurrentUser);
//RecursiveRegedit(Microsoft.Win32.Registry.LocalMachine);
MessageBox.Show("Done!");
}
//Recursive scan the registry.
void RecursiveRegedit(Microsoft.Win32.RegistryKey regBoot) {
if(regBoot == null) throw new ArgumentNullException("Null Item!");
string[] vals = regBoot.GetValueNames();
foreach(var v in vals) {
if(regBoot.GetValue(v) != null) {
string s = regBoot.GetValue(v).ToString();
if(s.StartsWith("C:", StringComparison.CurrentCultureIgnoreCase))
_RKeys.Add(regBoot); //Add to 'List'.
}
}
if(regBoot.SubKeyCount <= 0) //Exit.
return;
else { //Recursive.
string[] subs = regBoot.GetSubKeyNames();
foreach(string s in subs) {
try {//Try...catch the not accessible notes exception.
RecursiveRegedit(regBoot.OpenSubKey(s, Microsoft.Win32.RegistryKeyPermissionCheck.ReadWriteSubTree, System.Security.AccessControl.RegistryRights.FullControl));
}
catch {
}
}
}
regBoot.Close(); //Close.
}
/// <summary>
/// Show Progress bar form.
/// </summary>
void ShowProbar() {
ProgressBarForm proForm = new ProgressBarForm();
_IncHanler = new IncreaseProbarHandler(proForm.IncreaseProbarVal);
proForm.Show();
}
/// <summary>
/// Sub Thread to perform the progress bar.
/// </summary>
void ProThread() {
MethodInvoker mInvoker = new MethodInvoker(ShowProbar);
this.BeginInvoke(mInvoker);
Thread.Sleep(1000);
bool incResult = false; //The status each time when trying to increase the progress bar value.
do {
Thread.Sleep(5);
incResult = (bool)this.Invoke(this._IncHanler, new object[] { 2 });
} while(incResult);
}
Code in the Progress bar form:
/// <summary>
/// Increase the value of the progress bar.
/// </summary>
/// <param name="incVal">The value to increase.</param>
/// <returns>True if increase successful,otherwise false.</returns>
public bool IncreaseProbarVal(int incVal) {
if(incVal <= 0) throw new ArgumentOutOfRangeException("Increase value can't the a negative.");
if(proBar.Value + incVal < proBar.Maximum) {
proBar.Value += incVal;
return true;
}
else {
proBar.Value = proBar.Maximum;
return false;
}
}
Description:
I read the registry key value in the main form recursively using try catch statement.I started a new thread to perform the progress bar form.
The current issue is that,the progress bar form is not appear when running the app.It shows when the main form done(But the value of the progrss bar stay the same,or say not increase).
Some one said that whether I could sure the main jop is not block and has free time to perform the progress bar.I confuse about this and I just don't use the state 'block' or something else.So that must be some other question,or could you launch me someting and have some ideal?
Thanks for your time.
What a mess...a lot of un-necessary threading and Invoke()ing. =\
"The current issue is that,the progress bar form is not appear when running the app.It shows when the main form done"
Your call to RecursiveRegedit() is running on the main thread UI...thus nothing can be updated until the recursive calls are complete.
You need to run RecursiveRegedit() from another thread like you're doing with ProThread().
I have a class with a method (CreateDocument) that fires an event at the end. The event args contain a FixedDocument. In my MainWindow code I try to set a DocumentViewer's Document like:
void lpage_DocCreated(object sender, LabelDocumentEventArgs e)
{
this.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
new DispatcherOperationCallback(delegate
{
FixedDocument fd = e.doc;
documentViewer1.Document = fd;
documentViewer1.FitToWidth();
return null;
}), null);
}
I receive "The calling thread cannot access this object because a different thread owns it." on line documentViewer1.Document = fd;
I am able to update a progress bar in another event handler that the same method fires while it is working:
Int32 progress = Int32.Parse(sender.ToString());
progBar.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
new DispatcherOperationCallback(delegate
{
progBar.Value = progress;
return null;
}), null);
I can't figure out why I can't set the document when I'm essentially doing the same type of thing when I set the progress bar value.
The FixedDocument element also has thread-affinity. So if you are creating it in a separate thread than the documentViewer1, then you would get that exception.
Basically, anything that derives from DispatcherObject has a thread-affinity. FixedDocument derives from DispatcherObject, just like the viewer controls.
I have a Silverlight app. that has a basic animation where a rectangle is animated to a new position. The animation consists of two DoubleAnimation() - one transforms the X, the other transforms the Y. It works OK.
I basically want to block any other calls to this animate method until the first two animations have completed. I see that the DoubleAnimation() class has a Completed event it fires but I haven't been successful in constructing any kind of code that successfully blocks until both have completed.
I attempted to use Monitor.Enter on a private member when entering the method, then releasing the lock from one of the animations Completed event, but my attempts at chaining the two events (so the lock isn't released until both have completed) haven't been successful.
Here's what the animation method looks like:
public void AnimateRectangle(Rectangle rect, double newX, double newY)
{
var xIsComplete = false;
Duration duration = new Duration(new TimeSpan(0, 0, 0, 1, 350));
var easing = new ElasticEase() { EasingMode = EasingMode.EaseOut, Oscillations = 1, Springiness = 4 };
var animateX = new DoubleAnimation();
var animateY = new DoubleAnimation();
animateX.EasingFunction = easing;
animateX.Duration = duration;
animateY.EasingFunction = easing;
animateY.Duration = duration;
var sb = new Storyboard();
sb.Duration = duration;
sb.Children.Add(animateX);
sb.Children.Add(animateY);
Storyboard.SetTarget(animateX, rect);
Storyboard.SetTargetProperty(animateX, new PropertyPath("(Canvas.Left)"));
Storyboard.SetTarget(animateY, rect);
Storyboard.SetTargetProperty(animateY, new PropertyPath("(Canvas.Top)"));
animateX.To = newX;
animateY.To = newY;
sb.Begin();
}
EDIT (added more info)
I ran into this initially because I was calling this method from another method (as it processed items it made a call to the animation). I noticed that the items didn't end up where I expected them to. The new X/Y coordinates I pass in are based on the items current location, so if it was called multiple times before it finished, it ended up in the wrong location. As a test I added a button that only ran the animation once. It worked. However, if I click on the button a bunch of times in a row I see the same behavior as before: items end up in the wrong location.
Yes, it appears Silverlight animations are run on the main UI thread. One of the tests I tried I added two properties that flagged whether both animations had completed yet. In the AnimateRectange() method I checked them inside of a while loop (calling Thread.Sleep). This loop never completed (so it's definitely on the same thread).
So I created a queue to process the animations in order:
private void ProcessAnimationQueue()
{
var items = this.m_animationQueue.GetEnumerator();
while (items.MoveNext())
{
while (this.m_isXanimationInProgress || this.m_isYanimationInProgress)
{
System.Threading.Thread.Sleep(100);
}
var item = items.Current;
Dispatcher.BeginInvoke(() => this.AnimateRectangle(item.Rect.Rect, item.X, item.Y));
}
}
Then I call my initial routine (which queues up the animations) and call this method on a new thread. I see the same results.
As far as I am aware all of the animations in Silverlight are happening on the UI thread anyway. I am guessing that only the UI thread is calling this animation function anyway, so I am not sure that using locking will help. Do you really want to be blocking the entire thread or just preventing another animation from starting?
I would suggest something more like this:
private bool isAnimating = false;
public void AnimateRectangle(Rectangle rect, double newX, double newY)
{
if (isAnimating)
return;
// rest of animation code
sb.Completed += (sender, e) =>
{
isAnimating = false;
};
isAnimating = true;
sb.Begin();
}
Just keep track of whether or not you are currently animating with a flag and return early if you are. If you don't want to lose potential animations your other option is to keep some kind of a queue for animation which you could check/start when each animation has completed.
This question really peaked my interest. In fact I'm going to include it in my next blog post.
Boiling it down, just to be sure we are talking about the same thing, fundementally you don't want to block the call to AnimateRectangle you just want to "queue" the call so that once any outstanding call has completed its animation this "queued" call gets executed. By extension you may need to queue several calls if a previous call hasn't even started yet.
So we need two things:-
A means to treat what are essentially asynchronous operations (sb.Begin to Completed event) as a sequential operation, one operation only starting when the previous has completed.
A means to queue additional operations when one or more operations are yet to complete.
AsyncOperationService
Item 1 comes up in a zillion different ways in Silverlight due to the asynchronous nature of so many things. I solve this issue with a simple asynchronous operation runner blogged here. Add the AsyncOperationService code to your project.
AsyncOperationQueue
Its item 2 that really took my interest. The variation here is that whilst an existing set of operations are in progress there is demand to add another. For a general case solution we'd need a thread-safe means of including another operation.
Here is the bare-bones of a AsyncOperationQueue:-
public class AsyncOperationQueue
{
readonly Queue<AsyncOperation> myQueue = new Queue<AsyncOperation>();
AsyncOperation myCurrentOp = null;
public void Enqueue(AsyncOperation op)
{
bool start = false;
lock (myQueue)
{
if (myCurrentOp != null)
{
myQueue.Enqueue(op);
}
else
{
myCurrentOp = op;
start = true;
}
}
if (start)
DequeueOps().Run(delegate { });
}
private AsyncOperation GetNextOperation()
{
lock (myQueue)
{
myCurrentOp = (myQueue.Count > 0) ? myQueue.Dequeue() : null;
return myCurrentOp;
}
}
private IEnumerable<AsyncOperation> DequeueOps()
{
AsyncOperation nextOp = myCurrentOp;
while (nextOp != null)
{
yield return nextOp;
nextOp = GetNextOperation();
}
}
}
Putting it to use
First thing to do is convert your existing AnimateRectangle method into a GetAnimateRectangleOp that returns a AsyncOperation. Like this:-
public AsyncOperation GetAnimateRectangleOp(Rectangle rect, double newX, double newY)
{
return (completed) =>
{
// Code identical to the body of your original AnimateRectangle method.
sb.Begin();
sb.Completed += (s, args) => completed(null);
};
}
We need to hold an instance of the AsyncOperationQueue:-
private AsyncOperationQueue myAnimationQueue = new AsyncOperationQueue();
Finally we need to re-create AnimateRectangle that enqueues the operation to the queue:-
public void AnimateRectangle(Rectangle rect, double newX, double newY)
{
myAnimationQueue.Enqueue(GetAnimateRectangleOp(rect, newX, newY)
}