Stopping and starting tasks using CancellationTokenSource - c#

this is just a simplified version of what I am trying to do
public partial class MainWindow : Window
{
private CancellationTokenSource tokenSource = new CancellationTokenSource();
public MainWindow()
{
InitializeComponent();
PrintButtonText("None");
}
private void PrintButtonText(string buttonText)
{
Console.WriteLine("Update!");
Task.Factory.StartNew(() =>
{
while (!tokenSource.Token.IsCancellationRequested)
{
Console.WriteLine("Button Pressed Text: " + buttonText);
}
}, tokenSource.Token);
}
private void Button1_Click(object sender, RoutedEventArgs e)
{
tokenSource.Cancel();
PrintButtonText("Button1");
}
private void Button2_Click(object sender, RoutedEventArgs e)
{
tokenSource.Cancel();
PrintButtonText("Button2");
}
}
After I do
tokenSource.Cancel();
PrintButtonText("Button1");
It's not able to start the task again and continue to print my line. I need it to work this way for my program.
I want to stop the thread and start it again with some different parameters. How can I achieve this? thanks
EDIT:
Since I didn't get a solution with my simplified version, here is full code and what I am trying to do. Basically, on wpf window camera rendering starts on start. There is a button to start saving to file, but in order to save I have to update the config and start "pipeline" again.
public partial class MainWindow : Window
{
private Pipeline pipeline = new Pipeline(); // Create and config the pipeline to sream color and depth frames.
private CancellationTokenSource tokenSource = new CancellationTokenSource();
private bool saveDataToFile = false;
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Config cfg = SetupConfig(false);
PipelineProfile pp = pipeline.Start(cfg);
StartRenderFrames(pp);
}
private void StartRenderFrames(PipelineProfile pp)
{
Colorizer colorizer = new Colorizer(); // The colorizer processing block used to visualize the depth frames.
// Allocate bitmaps for rendring. Since the sample aligns the depth frames to the color frames, both of the images will have the color resolution
using (var p = pp.GetStream(Stream.Color) as VideoStreamProfile)
{
imgColor.Source = new WriteableBitmap(p.Width, p.Height, 96d, 96d, PixelFormats.Rgb24, null);
imgDepth.Source = new WriteableBitmap(p.Width, p.Height, 96d, 96d, PixelFormats.Rgb24, null);
}
Action<VideoFrame> updateColor = UpdateImage(imgColor);
Action<VideoFrame> updateDepth = UpdateImage(imgDepth);
Task.Factory.StartNew(() =>
{
while (!tokenSource.Token.IsCancellationRequested)
{
// Wait for the next available FrameSet
using (var frames = pipeline.WaitForFrames())
{
var colorFrame = frames.ColorFrame.DisposeWith(frames);
var depthFrame = frames.DepthFrame.DisposeWith(frames);
// We colorize the depth frame for visualization purposes, .
var colorizedDepth = colorizer.Process(depthFrame).DisposeWith(frames);
// Render the frames.
Dispatcher.Invoke(DispatcherPriority.Render, updateDepth, colorizedDepth);
Dispatcher.Invoke(DispatcherPriority.Render, updateColor, colorFrame);
}
}
}, tokenSource.Token);
}
private Config SetupConfig(bool saveDepthFile)
{
Config cfg = new Config();
cfg.EnableStream(Stream.Depth, 640, 480, framerate: 15);
cfg.EnableStream(Stream.Color, 640, 480, format: Format.Rgb8, framerate: 15);
if (saveDepthFile)
{
cfg.EnableRecordToFile(#"C:\temp\My_test111.bag");
}
return cfg;
}
static Action<VideoFrame> UpdateImage(Image img)
{
var wbmp = img.Source as WriteableBitmap;
return new Action<VideoFrame>(frame =>
{
using (frame)
{
var rect = new Int32Rect(0, 0, frame.Width, frame.Height);
wbmp.WritePixels(rect, frame.Data, frame.Stride * frame.Height, frame.Stride);
}
});
}
private void StartSaving_Button_Click(object sender, RoutedEventArgs e)
{
tokenSource.Cancel();
pipeline.Stop();
// This is where I have a problem. Rendering thread does not stop before I want to start again.
Config cfg = SetupConfig(true);
PipelineProfile pp = pipeline.Start(cfg);
StartRenderFrames(pp);
}
}

You should use Microsoft's Reactive Framework (aka Rx) - NuGet System.Reactive.Windows.Forms and add using System.Reactive.Linq; - then you can do this:
private void Form1_Load(object sender, EventArgs e)
{
IObservable<string> button1Clicks =
Observable
.FromEventPattern<EventHandler, EventArgs>(h => button1.Click += h, h => button1.Click -= h)
.Select(ep => "Button1");
IObservable<string> button2Clicks =
Observable
.FromEventPattern<EventHandler, EventArgs>(h => button2.Click += h, h => button2.Click -= h)
.Select(ep => "Button2");
IDisposable subscription =
button1Clicks
.Merge(button2Clicks)
.StartWith("None")
.Select(x => Observable.Timer(TimeSpan.Zero, TimeSpan.FromMilliseconds(500.0)).Select(n => x))
.Switch()
.ObserveOn(this)
.Subscribe(x => Console.WriteLine(x));
}
That's the entire code needed to make what you want to work.
The only thing that you need to do is move subscription out into a private field and then simply call subscription.Dispose() to shut this all down.
This is much simpler than messing with cancellation tokens.

Related

UI block when I use a timer

I try to move a grid in my window with an animation, but the animation doesn't happen, so I put a timer so that you can see the animation little by little but it still dont work :/
Here is my code:
class Animation
{
private int x_dropGridToBottom;
private Grid grid_dropGridToBottom;
private System.Timers.Timer t;
public Animation()
{
t = new System.Timers.Timer();
t.Interval = 500;
}
private void TimerElapsed_DropGridToBottom(object sender, ElapsedEventArgs e)
{
while (x_dropGridToBottom > 0)
{
x_dropGridToBottom--;
Application.Current.Dispatcher.Invoke(() =>
{
grid_dropGridToBottom.Margin = new Thickness(0, x_dropGridToBottom, 0, x_dropGridToBottom);
});
}
if (x_dropGridToBottom == 0)
t.Stop();
}
internal void DropGridToBottom(Grid grid, Window window)
{
grid.Margin = new Thickness(0, window.Height, 0, window.Height);
// 0 x 0 x (x = window.height)
// x = -y (y=412)
// x (=) 0
grid_dropGridToBottom = grid;
x_dropGridToBottom = Convert.ToInt32(window.Height);
t.Elapsed += new ElapsedEventHandler(TimerElapsed_DropGridToBottom);
t.Start();
}
}
And here is the code when I press the button that is supposed to start the animation :
private void Button_Click(object sender, RoutedEventArgs e)
{
Animation anim = new Animation();
anim.DropGridToBottom(grid_2, this);
}
Somebody has an idea of why and how I can make my grid move little by little but not directly in one go (as it does nowadays).
Thank you

How to increase number label when holding down mouse button?

C# Calling a function repeatedly to increase value of label automatically
This is a WPF program. It has a label on it (minuteTimerLabel) that displays a two digit number, starting from 00, 01, 02 and so on up to 99. I want two features from this label. First, when the left mouse button goes up on the label (simulating a click), the number increases by 1. I have been able to implement this. But I have problem with the second feature.
private void MinuteTimerLabel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
while(true)
{
if (timerMinute == 99)
{
return;
}
timerMinute += 1;
minuteTimerLabel.Content = String.Format("{0:00}", timerMinute);
Thread.Sleep(250);
}
}
When the left mouse button is held down on the label for a few seconds, the number should keep increasing one by one automatically.
timerMinute is a global int variable.
With my current code, the entire program locks up and nothing works. When I remove the while(true), number increases only once when pressing mouse down. If I release the mouse and press again, it works but again only once.
To avoid locking up the UI you need to use some form of async coding. I'd suggest using Microsoft's Reactive Framework.
NuGet "System.Reactive.Windows.Threading" to get the bits and add using System.Reactive.Linq; to the top of your code.
Then you can do this:
private IDisposable _subscription = null;
private void MinuteTimerLabel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_subscription =
Observable
.Interval(TimeSpan.FromMilliseconds(250.0))
.Select(x => String.Format("{0:00}", x))
.ObserveOnDispatcher()
.Subscribe(x => MinuteTimerLabel.Content = x);
}
private void MinuteTimerLabel_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_subscription.Dispose();
}
It's super simple and super clean.
private IDisposable _subscription = null;
private int _counter = 0;
private void MinuteTimerLabel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_subscription =
Observable
.Interval(TimeSpan.FromMilliseconds(250.0))
.Select(x => String.Format("{0:00}", x))
.ObserveOnDispatcher()
.Subscribe(x => MinuteTimerLabel.Content = _counter++ % 100);
}
private void MinuteTimerLabel_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_subscription.Dispose();
}
Try the below code:
The code will run in the another thread un-blocking the Main Thread.
private void MinuteTimerLabel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
new Thread(Timer).Start();
}
private void Timer() {
while(true)
{
if (timerMinute == 99)
{
return;
}
timerMinute += 1;
minuteTimerLabel.Content = String.Format("{0:00}", timerMinute);
Thread.Sleep(250);
}
}
Good fit for a CancellationToken and Async Await Pattern
private CancellationTokenSource _cs;
private int _timerMinute =0;
private async void Label1_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_cs?.Cancel();
_cs?.Dispose();
_cs = new CancellationTokenSource();
try
{
while (!_cs.Token.IsCancellationRequested)
{
await Task.Delay(200, _cs.Token);
Label1.Content = $"{++_timerMinute:00}";
}
}
catch (OperationCanceledException) {}
}
private void Label1_OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_cs?.Cancel();
_cs?.Dispose();
}
Also if you wanted to be tricky you could add weight to it the more the mouse is down, by altering the delay
You need to bind couple of event in this case, PreviewMouseDown and PreviewMouseUp. Then calculate time different between it and increment the value for label on ticks/secs.
Like this
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
PreviewMouseDown += Window3_PreviewMouseDown;
PreviewMouseUp += Window3_PreviewMouseUp;
}
DateTime mouseDown;
private void Window3_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
mouseDown = DateTime.Now;
}
private void Window3_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
var seconds = DateTime.Now.Subtract( mouseDown ).Seconds;
//Condition code goes here..
minuteTimerLabel.Content = seconds;
}
}

System.Threading.Tasks.TaskCanceledException: 'A task was canceled.' when Closing App

I have a thread that change the field
private void SongChange(object sender, RoutedEventArgs e)
{
SongChangeAction();
songLength = (int)BGMPlayer.NaturalDuration.TimeSpan.TotalSeconds;
songProgress = new Thread(SongProgressUpdate) { IsBackground = true };
songProgress.Start();
}
private void SongProgressUpdate()
{
while (true)
{
Dispatcher.Invoke(() => { workingResources.SongProgress = BGMPlayer.Position.TotalSeconds / songLength * 100; });
Thread.Sleep(1000);
}
}
Note: songLength is a double, BGMPlayer is a MediaElement to play music in background, workingResources.SongProgress is a double bind to a Progress Bar
When I close the Program using "X" button or on taskbar, The program threw exception System.Threading.Tasks.TaskCanceledException Even I try to set the thread IsBackground = true but it work not throw exception If I stop the program using Visual Studio.
The Program Throw exception at line:
Dispatcher.Invoke(() => { workingResources.SongProgress = BGMPlayer.Position.TotalSeconds / songLength * 100; });
I can prevent it by using try/catch but I want to find something more efficient
Try using a CancellationToken on close of the window:
private readonly CancellationTokenSource _shutDown = new CancellationTokenSource();
public WindowName()
{
this.Closed =+ (s, e) => this._shutDown.Cancel();
}
private void SongChange(object sender, RoutedEventArgs e)
{
SongChangeAction();
songLength = (int)BGMPlayer.NaturalDuration.TimeSpan.TotalSeconds;
songProgress = Task.Factory.StartNew(SongProgressUpdate, this._shutDown.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
private void SongProgressUpdate()
{
while (!this._shutDown.IsCancellationRequested)
{
Dispatcher.Invoke(() => { workingResources.SongProgress = BGMPlayer.Position.TotalSeconds / songLength * 100; });
Thread.Sleep(1000);
}
}
Try using Microsoft's Reactive Framework. Then you can do this:
private void SongChange(object sender, RoutedEventArgs e)
{
SongChangeAction();
songLength = (int)BGMPlayer.NaturalDuration.TimeSpan.TotalSeconds;
IDisposable subscription =
Observable
.Interval(TimeSpan.FromSeconds(1.0))
.ObserveOnDispatcher()
.Subscribe(x => workingResources.SongProgress = BGMPlayer.Position.TotalSeconds / songLength * 100);
}
You just need to make sure that you save a reference to subscription and call .Dispose() on it when you are closing your app.
NuGet "System.Reactive" and "System.Reactive.Windows.Threading", and add using System.Reactive.Linq; to your code.

Accessing dictionary items added by another thread

Here is my code -
public partial class MainWindow : Window
{
ConcurrentDictionary<int,BitmapSource> Cache = new ConcurrentDictionary<int,BitmapSource>(5, 199);
int width = 720;
int height = 480;
int stride = (width * 3 / 4 ) * 4 + 4;
int currentframe = 0;
public MainWindow()
{
InitializeComponent();
Thread t = new Thread(() =>
{
while (true)
{
for (int i = -9; i < 9; i++)
{
if (!Cache.ContainsKey(currentframe + i))
{
RenderFrameToCache(currentframe + i);
}
}
Thread.Sleep(500);
}
});
t.Start();
}
private object locker = new object();
private void RenderFrameToCache(int frame)
{
byte[] pixels;
//Create byte array
BitmapSource img = BitmapSource.Create(width, height, 96, 96, PixelFormats.Bgr24, null, pixels, stride);
Cache.AddOrUpdate(frame, img, (x,y) => img);
}
private BitmapSource GetBmpSource(int frame)
{
if (Cache.ContainsKey(frame))
{
return Cache[frame];
}
else
{
RenderFrameToCache(frame);
return Cache[frame];
}
}
private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
Cache.Clear();
image.Source = new BitmapImage();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
currentframe++;
image.Source = GetBmpSource(currentframe);
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
currentframe--;
image.Source = GetBmpSource(currentframe);
}
}
The second thread is supposed to fill the dictionary with items so that they are on hand when the Window wants to display them.
Whenever a button is pressed, there is an InvalidOperationException. What is the problem?
Use thread safe ConcurrentDictionary.
From MSDN:
All these operations [TryAdd, TryUpdate, ...] are atomic and are thread-safe with regards to
all other operations on the ConcurrentDictionary class.
[...] For modifications and write operations to the dictionary,
ConcurrentDictionary uses fine-grained locking to ensure
thread safety. (Read operations on the dictionary are performed in a
lock-free manner.)
I solved the problem.
I had to use BitmapSource.Freeze each time I added a new BitmapSource.
This link is what explained it - Multi threading in WPF using C# (with background worker)

WyUpdate - C# Console application, update not responding working... how to run synchronous?

If anyone has any feedback on why my WyUpdate application doesn't want to work please let me know.
I'm trying to create a basic console application which launches the WyUpdate process, and then on completion executes my main application (which has just been updated).
Following the instructions on http://wyday.com/wybuild/help/silent-update-windows-service.php gives me no luck at all. The application runs, and executes the "ForceCheckForUpdate" process, but I don't receive any feedback :(
here is a complete listing of my code.
I've added a while loop to hopefully catch the response from the auBackend, but that doesn't seem to work. is there an easy way to run the process synchronously and wait for the response before closing the application?
Thanks in advance.
using System;
using System.Threading;
using wyDay.Controls;
namespace NPS.CeAUpdateLauncher
{
class Program
{
private static AutomaticUpdaterBackend auBackend;
private static bool receivedFeedback;
static void Main(string[] args)
{
auBackend = new AutomaticUpdaterBackend
{
//TODO: set a unique string.
// For instance, "appname-companyname"
GUID = "CeALauncher_AutoUpdate",
// With UpdateType set to Automatic, you're still in
// charge of checking for updates, but the
// AutomaticUpdaterBackend continues with the
// downloading and extracting automatically.
UpdateType = UpdateType.Automatic,
};
auBackend.CheckingFailed += auBackend_CheckingFailed;
auBackend.UpdateAvailable += auBackend_UpdateAvailable;
auBackend.DownloadingFailed += auBackend_DownloadingFailed;
auBackend.ExtractingFailed += auBackend_ExtractingFailed;
auBackend.ReadyToBeInstalled += auBackend_ReadyToBeInstalled;
auBackend.UpdateSuccessful += auBackend_UpdateSuccessful;
auBackend.UpdateFailed += auBackend_Failed;
// Initialize() and AppLoaded() must be called after events have been set.
// Note: If there's a pending update to be installed, wyUpdate will be
// started, then it will talk back and say "ready to install,
// you can close now" at which point your app will be closed.
auBackend.Initialize();
auBackend.AppLoaded();
if (!auBackend.ClosingForInstall)
{
//TODO: do your normal service work
CheckForUpdates();
}
//
while(!receivedFeedback)
Thread.Sleep(10000);
}
static void CheckForUpdates()
{
// Only ForceCheckForUpdate() every N days!
// You don't want to recheck for updates on every app start.
if (//(DateTime.Now - auBackend.LastCheckDate).TotalDays > 10 &&
auBackend.UpdateStepOn == UpdateStepOn.Nothing)
{
auBackend.ForceCheckForUpdate();
}
}
static void auBackend_CheckingFailed(object sender, FailArgs e)
{
receivedFeedback = true;
}
static void auBackend_UpdateAvailable(object sender, EventArgs e)
{
receivedFeedback = true;
}
static void auBackend_DownloadingFailed(object sender, FailArgs e)
{
receivedFeedback = true;
}
static void auBackend_ExtractingFailed(object sender, FailArgs e)
{
receivedFeedback = true;
}
static void auBackend_ReadyToBeInstalled(object sender, EventArgs e)
{
// ReadyToBeInstalled event is called when
// either the UpdateStepOn == UpdateDownloaded or UpdateReadyToInstall
if (auBackend.UpdateStepOn == UpdateStepOn.UpdateReadyToInstall)
{
//TODO: Delay the installation of the update until
// it's appropriate for your app.
//TODO: Do any "spin-down" operations. auBackend.InstallNow() will
// exit this process using Environment.Exit(0), so run
// cleanup functions now (close threads, close running programs,
// release locked files, etc.)
// here we'll just close immediately to install the new version
auBackend.InstallNow();
}
receivedFeedback = true;
}
static void auBackend_UpdateSuccessful(object sender, SuccessArgs e)
{
receivedFeedback = true;
}
static void auBackend_Failed(object sender, FailArgs e)
{
receivedFeedback = true;
}
}
}
With the code I attached in my initial post, the problem was that the console application would be running indefinitely. I thought that was because the wyUpdate process never returned a value. I found out it was because I didn't raise all events.
After implementing the eventhandler for "UpToDate" I received a response and was able to set the indicator to have the application exit successfully.
I also replaced the boolean indicator and the 'while' method of waiting for a response to the 'resetEvent' as per the vendor's instructions, and it is working very well.
static readonly ManualResetEvent resetEvent = new ManualResetEvent(false);
static void Main(string[] args)
{
...
auBackend.UpToDate += auBackend_UpToDate;
...
// Blocks until "resetEvent.Set()" on another thread
resetEvent.WaitOne();
}
static void auBackend_UpToDate(object sender, SuccessArgs e)
{
resetEvent.Set();
}
Thanks for the responses.
If anyone ever needs a very versatile application updater solution, I would suggest the wyBuild/wyUpdate package.
Njoy!
If you're able to use the Reactive Extensions for .NET (Rx) then I've got an easier alternative.
As long as the AutomaticUpdaterBackend class is firing its events on a background thread then this code should do what you want:
void Main(string[] args)
{
var auBackend = new AutomaticUpdaterBackend
{
GUID = "CeALauncher_AutoUpdate",
UpdateType = UpdateType.Automatic,
};
var checkingFailed = Observable.FromEvent<FailArgs>(h => auBackend.CheckingFailed += h, h => auBackend.CheckingFailed -= h);
var updateAvailable = Observable.FromEvent<EventArgs>(h => auBackend.UpdateAvailable += h, h => auBackend.UpdateAvailable -= h);
var downloadingFailed = Observable.FromEvent<FailArgs>(h => auBackend.DownloadingFailed += h, h => auBackend.DownloadingFailed -= h);
var extractingFailed = Observable.FromEvent<FailArgs>(h => auBackend.ExtractingFailed += h, h => auBackend.ExtractingFailed -= h);
var readyToBeInstalled = Observable.FromEvent<EventArgs>(h => auBackend.ReadyToBeInstalled += h, h => auBackend.ReadyToBeInstalled -= h);
var updateSuccessful = Observable.FromEvent<SuccessArgs>(h => auBackend.UpdateSuccessful += h, h => auBackend.UpdateSuccessful -= h);
var updateFailed = Observable.FromEvent<FailArgs>(h => auBackend.UpdateFailed += h, h => auBackend.UpdateFailed -= h);
var readyToBeInstalledStepReady = readyToBeInstalled.Where(e => auBackend.UpdateStepOn == UpdateStepOn.UpdateReadyToInstall);
var readyToBeInstalledStepNotReady = readyToBeInstalled.Where(e => auBackend.UpdateStepOn != UpdateStepOn.UpdateReadyToInstall);
var failed = Observable.Merge(new []
{
checkingFailed.Select(e => "Checking Failed"),
downloadingFailed.Select(e => "Downloading Failed"),
extractingFailed.Select(e => "Extracting Failed"),
updateFailed.Select(e => "Update Failed"),
readyToBeInstalledStepNotReady.Select(e => "Update Not Ready"),
});
var success = Observable.Merge(new []
{
readyToBeInstalledStepReady.Select(e => "Update Ready"),
updateSuccessful.Select(e => "Update Successful"),
});;
failed.Subscribe(e => Console.Error.WriteLine(e));
success.Subscribe(e => Console.WriteLine(e));
readyToBeInstalledStepReady.Subscribe(e => auBackend.InstallNow());
auBackend.Initialize();
auBackend.AppLoaded();
if (!auBackend.ClosingForInstall)
{
if (auBackend.UpdateStepOn == UpdateStepOn.Nothing)
{
auBackend.ForceCheckForUpdate();
}
}
success.Merge(failed).First();
}
Yes, there aren't any module-level variables and no event handler methods. Just locals and lambdas. :-)

Categories

Resources