Async Tasks Pause Resume of Loop [duplicate] - c#

This question already has answers here:
A pattern to pause/resume an async task?
(5 answers)
Closed 5 years ago.
I have a sample code, taken from MSDN, to which I would like implement the Pause / Resume functionality within while loop. Could anybody propose a solution / parern which would hande this?
private async void startButton_Click(object sender, RoutedEventArgs e)
{
resultsTextBox.Clear();
// Instantiate the CancellationTokenSource.
cts = new CancellationTokenSource();
try
{
await AccessTheWebAsync(cts.Token);
resultsTextBox.Text += "\r\nDownloads complete.";
}
catch (OperationCanceledException)
{
resultsTextBox.Text += "\r\nDownloads canceled.\r\n";
}
catch (Exception)
{
resultsTextBox.Text += "\r\nDownloads failed.\r\n";
}
cts = null;
}
private void cancelButton_Click(object sender, RoutedEventArgs e)
{
if (cts != null)
{
cts.Cancel();
}
}
async Task AccessTheWebAsync(CancellationToken ct)
{
HttpClient client = new HttpClient();
// Make a list of web addresses.
List<string> urlList = SetUpURLList();
// ***Create a query that, when executed, returns a collection of tasks.
IEnumerable<Task<int>> downloadTasksQuery =
from url in urlList select ProcessURL(url, client, ct);
// ***Use ToList to execute the query and start the tasks.
List<Task<int>> downloadTasks = downloadTasksQuery.ToList();
// ***Add a loop to process the tasks one at a time until none remain.
while (downloadTasks.Count > 0)
{
// Identify the first task that completes.
Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks);
// ***Remove the selected task from the list so that you don't
// process it more than once.
downloadTasks.Remove(firstFinishedTask);
// Await the completed task.
int length = await firstFinishedTask;
resultsTextBox.Text += String.Format("\r\nLength of the download: {0}", length);
}
}

There is an MSDN article that solves this using a PauseToken (similar to a CancellationToken).
Here's the sample code from that article that demonstrates this concept:
namespace PauseTokenTestApp
{
public class PauseTokenSource
{
private volatile TaskCompletionSource<bool> m_paused;
internal static readonly Task s_completedTask = Task.FromResult(true);
public bool IsPaused
{
get { return m_paused != null; }
set
{
if (value)
{
Interlocked.CompareExchange(
ref m_paused, new TaskCompletionSource<bool>(), null);
}
else
{
while (true)
{
var tcs = m_paused;
if (tcs == null) return;
if (Interlocked.CompareExchange(ref m_paused, null, tcs) == tcs)
{
tcs.SetResult(true);
break;
}
}
}
}
}
public PauseToken Token { get { return new PauseToken(this); } }
internal Task WaitWhilePausedAsync()
{
var cur = m_paused;
return cur != null ? cur.Task : s_completedTask;
}
}
public struct PauseToken
{
private readonly PauseTokenSource m_source;
internal PauseToken(PauseTokenSource source) { m_source = source; }
public bool IsPaused { get { return m_source != null && m_source.IsPaused; } }
public Task WaitWhilePausedAsync()
{
return IsPaused ?
m_source.WaitWhilePausedAsync() :
PauseTokenSource.s_completedTask;
}
}
class Program
{
static void Main()
{
var pts = new PauseTokenSource();
Task.Run(() =>
{
while (true)
{
Console.ReadLine();
pts.IsPaused = !pts.IsPaused;
}
});
SomeMethodAsync(pts.Token).Wait();
}
public static async Task SomeMethodAsync(PauseToken pause)
{
for (int i = 0; i < 100; i++)
{
Console.WriteLine(i);
await Task.Delay(100);
await pause.WaitWhilePausedAsync();
}
}
}
}

Related

How to pause or stop "CSharpScript" running?

I'm using Roslyn's scripting API in my application, code snippet as following:
public class ScriptEngine
{
public static string CodeText;
public static event Action<string[]> CompileErrorEvent;
public static async Task<bool> RunScriptAsync(CancellationToken ct)
{
try
{
var scriptResult = await CSharpScript.RunAsync(CodeText, null, new ScriptHost(), null, ct);
return true;
}
catch (Microsoft.CodeAnalysis.Scripting.CompilationErrorException ex)
{
List<string> result = new List<string>();
foreach (var item in ex.Diagnostics)
{
result.Add(item.ToString());
}
if (result.Count > 0)
{
CompileErrorEvent?.Invoke(result.ToArray());
}
return false;
}
catch (Exception ex)
{
IMCP_Base.Dialog.Show.SimpleError("脚本运行", ex.Message, "修改脚本");
return false;
}
}
.......
}
public static CancellationTokenSource ScriptCTS;
private async void btnScriptRun_ItemClick(object sender, ItemClickEventArgs e)
{
ScriptCTS = new CancellationTokenSource();
if (CheckScriptEditorIsNotNull())
{
Script.ScriptEngine.CodeText = ScriptEditor.GetCode();
bool runSuccess = await Script.ScriptEngine.RunScriptAsync(ScriptCTS.Token);
}
}
private void btnScriptStop_ItemClick(object sender, ItemClickEventArgs e)
{
ScriptCTS?.Cancel();
}
CSharpScript.RunAsync method runs well, but when I click ScriptStop button, ScriptCTS?.Cancel() can't cancel running script.
How can I stop or pause a script running?
If you want cancellation points within your scripts, you can use globals, and pass in the CancelationToken, which you can then check in your scripts for cancellation.

Show Form Message using non-Main UI Thread

I have a project that without usign any form/button or nothing like that, connects with a Websocket and using async methods receives some message(on a form created by myself) that is supposed to appear on the top-right corner of the screen.
But this message can appear from time to time (2 or 3 minutes) on the screen if the websocket doesn't say that it must stop. And this message can be big enough, that in order to make it look better I make my message appear in more than one form.
It causes an impression that it's a notification. So my class that connects with the websocket and receives the message async, calls another class using a thread that is a controller. The purpose of the controller is from time to time, show that message in various new form() notifications and obviously don't do it if the websocket doesn't return any message.
But when i call the form.show the program stops working.
I've looked around stackoverflow already, but the ideas that i've found didn't seem to work.
Some say that I should use invoke, but it kept giving error saying that
"Invoke or BeginInvoke cannot be called on a control until the window handle has been created", tried to solve like this: C# calling form.show() from another thread but it didn't work.
Some said that I should use .showDialog instead of .show, but it doesn't appear to be good, because it waits the window to be closed to terminate the method and as I said I need to open more than one notification at the same time.
Some said that the form was open with .show, but it was open for a very little period of time. But i couldn't notice if that was the case and even if it was i couldn't solve it. Well, what matter is that i'm stuck and i don't know what to do more.
Edited with Code:
//Main
Application.Run(new SocketService());
//SocketService class
public SocketService()
{
alerta = null;
while (true)
{
try
{
//Console.WriteLine("Nome do Usúario:" + Environment.UserName);
Thread.Sleep(2000);
Connect("ws://192.168.120.38:9091").Wait();
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
public static async Task Connect(string uri)
{
ClientWebSocket webSocket = null;
try
{
webSocket = new ClientWebSocket();
await webSocket.ConnectAsync(new Uri(uri), CancellationToken.None);
await Login(webSocket);
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (webSocket != null)
webSocket.Dispose();
lock (consoleLock)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("WebSocket closed.");
Console.ResetColor();
}
}
}
private static async Task Login(ClientWebSocket webSocket)
{
ArraySegment<Byte> buffer = new ArraySegment<byte>(encoder.GetBytes( "{\"event\":\"loginBrowser\",\"data\":{\"login\":\"000000003077\",\"data\":\"1\"}}"));
await webSocket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
if (webSocket.State == WebSocketState.Open)
{
if (ShowMessage.created != true)
{
var dummy = new Control(); // to initialize SynchronizationContext
_sync = SynchronizationContext.Current;
new Thread(ThreadProc).Start();
}
await Receive(webSocket);
}
}
private static async Task Receive(ClientWebSocket webSocket)
{
while (webSocket.State == WebSocketState.Open)
{
ArraySegment<Byte> buffer = new ArraySegment<byte>(new Byte[256]);
var result = await webSocket.ReceiveAsync(buffer, CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Close)
{
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
}
else
{
if (result.EndOfMessage)
{
message += encoder.GetString(buffer.ToArray());
SendMessage(message);
}
else
{
message += encoder.GetString(buffer.ToArray());
}
}
}
}
public static void ShowFormFromAnotherThread(string text)
{
_sync.Post(SendOrPostCallback, text);
}
private static void SendOrPostCallback(object state)
{
var form = new Notification();
form.Text = (string)state;
form.Show();
}
private static void ThreadProc()
{
while (true)
{
Thread.Sleep(2000); // wait imitation
ShowFormFromAnotherThread("HI");
}
}
/*Notification is my form and depending on where I put this part:
var dummy = new Control(); // to initialize SynchronizationContext
_sync = SynchronizationContext.Current;
new Thread(ThreadProc).Start();
Or i doesn't call login or doesn't enter receive() method or the best case It receives the information
calls the threadProc and the ShowFormFromAnotherThread but doesn't enter SednOrPostCallBack*/
using System.Threading;
using System.Windows.Forms;
namespace ConsoleThreadSync
{
internal class Program
{
private static void Main(string[] args)
{
Application.Run(new App());
}
}
public class App : ApplicationContext
{
private readonly SynchronizationContext _sync;
public App()
{
var dummy = new Control(); // to initialize SynchronizationContext
_sync = SynchronizationContext.Current;
new Thread(ThreadProc).Start();
}
public void ShowFormFromAnotherThread(string text)
{
_sync.Post(SendOrPostCallback, text);
}
private void SendOrPostCallback(object state)
{
var form = new Form1();
form.Text = (string)state;
form.Show();
}
private void ThreadProc()
{
while (true)
{
Thread.Sleep(2000); // wait imitation
ShowFormFromAnotherThread("HI");
}
}
}
}
Try to call this:
var dummy = new Control(); // to initialize SynchronizationContext
_sync = SynchronizationContext.Current;
from a contructor SocketService() and not from async methods. This is an initialization code and it must call from main thread.
Okay, after reading a little bit more, the solution that kind worked out was this one, but the only way of using the
.show from the notifician is to use the Application.DoEvents and I've been warned from the sources that I've looked into
that this method should not be used, unless is the only option, because It can cause some problems with the Threads and other things.
So unless someone can give me another hint or clue about what to do, I have two options or use this method and try to fix some other bug
that It can cause or use the .showDialog because don't know why It works without any other problem, but to use .showDialog I've
to use another thread where I create and show the notification because if I don't do, the loop will stop at each iteration
in order to wait the .showDialog be closed. And as it isn't a problem I want to avoid using a lot of threads, because it can cause
another problem with the sync between them:
namespace ReiDoCSharp
{
class ShowMessage
{
private static RootObject alerta;
public static bool created;
private static int startPosition;
public static void setStartPosition(int start)
{
if (start < startPosition)
{
startPosition = start;
}
}
public RootObject getAlerta()
{
return ShowMessage.alerta;
}
public void setAlerta(RootObject root)
{
ShowMessage.alerta = root;
}
private static void DoWork()
{
while (true)
{
if (created != true)
{
created = true;
}
if (alerta != null)
{
string mensagem = "";
if ((alerta.data.Informacoes[1] != "") && (alerta.data.Informacoes[1] != null))
{
mensagem += alerta.data.Informacoes[1];
}
if ((alerta.data.Informacoes[0] != "") && (alerta.data.Informacoes[0] != null))
{
mensagem += alerta.data.Informacoes[0];
}
if (mensagem != "")
{
startPosition = 5;
string[] messages = mensagem.Split(new[] { "<br><br>" }, StringSplitOptions.None);
foreach (string message in messages)
{
Notification popup = new Notification();
popup.label1.Text = message;
popup.TopMost = true;
popup.Show();
Application.DoEvents();
/*Solution with the ShowDialog would be:
Task.Run(() => showNotification(message));
*/
}
}
}
Thread.Sleep(5000);
}
}
//Then I won't need to use Application.DoEvents, but would have to create more threads
private static Task showNotification(string message)
{
Notification popup = new Notification();
popup.label1.Text = message;
popup.TopMost = true;
popup.ShowDialog();
}
public static Task createPopupsAsync()
{
Task.Run(() => DoWork());
}
}
}
namespace ReiDoCSharp
{
class SocketService
{
private static object consoleLock = new object();
private const bool verbose = true;
private static readonly TimeSpan delay = TimeSpan.FromMilliseconds(3000);
private static UTF8Encoding encoder = new UTF8Encoding();
private static string message;
private static RootObject alerta;
public SocketService()
{
Begin();
}
public static void Begin()
{
alerta = null;
while (true)
{
try
{
Thread.Sleep(2000);
Connect("ws://192.168.120.38:9091").Wait();
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
public static async Task Connect(string uri)
{
ClientWebSocket webSocket = null;
try
{
webSocket = new ClientWebSocket();
await webSocket.ConnectAsync(new Uri(uri), CancellationToken.None);
await Login(webSocket);
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (webSocket != null)
webSocket.Dispose();
lock (consoleLock)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("WebSocket closed.");
Console.ResetColor();
}
}
}
private static async Task Login(ClientWebSocket webSocket)
{
ArraySegment<Byte> buffer = new ArraySegment<byte>(encoder.GetBytes("{\"event\":\"loginBrowser\",\"data\":{\"OPERADOR\":\"000000003077\",\"NRORG\":\"1\"}}"));
await webSocket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
if (webSocket.State == WebSocketState.Open)
{
Task.Factory.StartNew(() => ShowMessage.createPopupsAsync());
await Receive(webSocket);
}
}
private static async Task Receive(ClientWebSocket webSocket)
{
while (webSocket.State == WebSocketState.Open)
{
ArraySegment<Byte> buffer = new ArraySegment<byte>(new Byte[256]);
var result = await webSocket.ReceiveAsync(buffer, CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Close)
{
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
}
else
{
if (result.EndOfMessage)
{
message += encoder.GetString(buffer.ToArray());
SendMessage(message);
}
else
{
message += encoder.GetString(buffer.ToArray());
}
}
}
}
private static void LogStatus(bool receiving, byte[] buffer, int length, string assunto)
{
lock (consoleLock)
{
Console.ForegroundColor = receiving ? ConsoleColor.Green : ConsoleColor.Yellow;
if (verbose)
{
Console.WriteLine(encoder.GetString(buffer) + " " + assunto);
}
Console.ResetColor();
}
}
private static void SendMessage(string message)
{
message = message.Replace("event", "evento");
message = message.Replace("\0", "");
JavaScriptSerializer js = new JavaScriptSerializer();
RootObject mess = js.Deserialize<RootObject>(message);
if (mess.data.Informacoes[1] != "")
{
mess.data.Informacoes[1] += "<br>";
}
if (alerta == null)
{
alerta = mess;
}
else
{
if ((mess.data.Quantidade[0] != 0) && (mess.data.Quantidade == null))
{
if ((mess.data.Quantidade[0] == -1) && (mess.data.Informacoes[0] == ""))
{
alerta = null;
}
else
{
alerta = mess;
}
}
else if (mess.data.Quantidade[0] == 0)
{
alerta = null;
}
if ((mess.data.Quantidade[1] != 0) && (mess.data.Informacoes[1] != ""))
{
alerta = mess;
}
}
new ShowMessage().setAlerta(alerta);
message = "";
}
}
}

Label animation inside a switch case c#

On the face of it it seems quite simple.
I have a switch case and if the condition is met I would like to print text to a label with a animation.
in this case a type writer animation.
I have already made the animation however I cant seem to integrate a similar version it into the switch case itself.
Any help?
Type Writer Animation code c#:
public partial class Form1 : Form
{
int _charIndex = 0;
string _text = "This is a test.";
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
_charIndex = 0;
label1.Text = string.Empty;
Thread t = new Thread(new ThreadStart(this.TypewriteText));
t.Start();
}
private void TypewriteText()
{
while (_charIndex < _text.Length)
{
Thread.Sleep(50);
label1.Invoke(new Action(() =>
{
label1.Text += _text[_charIndex];
}));
_charIndex++;
}
}
}
}
And the animation code needs to be placed into this:
Switch case code:
void TestEngine(object sender, SpeechRecognizedEventArgs e)
{
switch (e.Result.Text)
{
case "Test":
//Label animation code goes here
break;
Thanks in advance!
Short answer - move the code in a method and call it from anywhere you want.
Long answer
While it works, it doesn't make sense because all the worker thread does is sleeping and then calling the UI thread. System.Windows.Forms.Timer based approach would be much appropriate for this concrete case. The "modern" approach would be based on async/await. If you need flexibility, the last one is the best choice. But whatever you choose, you'll hit a reentrancy problem at some point and will need to handle it. The best would be to prepare some helper utility class and use it from anywhere. Here is an example:
using System;
using System.Drawing;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Tests
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new TestForm());
}
class TestForm : Form
{
public TestForm()
{
var label = new Label { Parent = this, AutoSize = true, Top = 8, Left = 8 };
animateHelper = new AnimateHelper(label);
int left = 8;
foreach (var action in (ButtonAction[])Enum.GetValues(typeof(ButtonAction)))
{
var button = new Button { Parent = this, AutoSize = true, Text = action.ToString(), Left = left };
button.Top = DisplayRectangle.Bottom - button.Height - 8;
button.Click += (sender, e) => Execute(action);
left += button.Width + 8;
}
}
protected override void Dispose(bool disposing)
{
if (disposing && animateHelper != null) animateHelper.Cancel();
base.Dispose(disposing);
}
enum ButtonAction { TypewriteText, RepeatText, Cancel }
private void Execute(ButtonAction action)
{
// the original question
switch (action)
{
case ButtonAction.TypewriteText:
TypewriteText("This is a typewriter text animantion test.");
break;
case ButtonAction.RepeatText:
RepeatText("This is a repeating text animantion test.");
break;
case ButtonAction.Cancel:
animateHelper.Cancel();
break;
}
}
AnimateHelper animateHelper;
void TypewriteText(string text)
{
animateHelper.Execute(async (output, ct) =>
{
bool clear = true;
try
{
if (string.IsNullOrEmpty(text)) return;
output.ForeColor = Color.Blue;
for (int length = 1; ; length++)
{
if (ct.IsCancellationRequested) return;
output.Text = text.Substring(0, length);
if (length == text.Length) break;
await Task.Delay(50, ct);
}
clear = false;
}
finally { if (clear) output.Text = string.Empty; }
});
}
void RepeatText(string text)
{
animateHelper.Execute(async (output, ct) =>
{
try
{
if (string.IsNullOrEmpty(text)) return;
output.ForeColor = Color.Red;
while (true)
{
for (int length = 1; length <= text.Length; length++)
{
if (ct.IsCancellationRequested) return;
output.Text = text.Substring(text.Length - length);
await Task.Delay(50, ct);
}
for (int pad = 1; pad < text.Length; pad++)
{
if (ct.IsCancellationRequested) return;
output.Text = new string(' ', pad) + text.Substring(0, text.Length - pad);
await Task.Delay(50, ct);
}
if (ct.IsCancellationRequested) return;
output.Text = string.Empty;
await Task.Delay(250, ct);
}
}
finally { output.Text = string.Empty; }
});
}
}
class AnimateHelper
{
Label output;
Task task;
CancellationTokenSource cts;
public AnimateHelper(Label output) { this.output = output; }
void Reset()
{
if (cts != null) { cts.Dispose(); cts = null; }
task = null;
}
public void Cancel() { DontCare(CancelAsync()); }
async Task CancelAsync()
{
if (task != null && !task.IsCompleted)
{
try { cts.Cancel(); } catch { }
try { await task; } catch { }
}
Reset();
}
public void Execute(Func<Label, CancellationToken, Task> action) { DontCare(ExecuteAsync(action)); }
async Task ExecuteAsync(Func<Label, CancellationToken, Task> action)
{
await CancelAsync();
cts = new CancellationTokenSource();
task = action(output, cts.Token);
try { await task; } catch { }
Reset();
}
// make compiler happy
static void DontCare(Task t) { }
}
}
}

Error with waithandles

I have written a simple Producer-Consumer queue. It keeps throwing an error that the safe handle is disposed.
namespace ConsoleApplication1
{
public class ProducerConsumer:IDisposable
{
private Queue<string> tasks = new Queue<string>();
Thread work;
readonly object obj=new object();
EventWaitHandle wh = new AutoResetEvent(true);
public ProducerConsumer()
{
work = new Thread(doWork);
work.Start();
}
public void doWork()
{
while (true)
{
string task = null;
wh.WaitOne();
lock (obj){
if (tasks.Count > 0)
{
task = tasks.Dequeue();
if (task == null) return;
else
{
Console.WriteLine("Performing task: " + task);
Thread.Sleep(1000); // simulate work...
}
}
}
}
}
public void ShutDown()
{
tasks.Enqueue(null);
wh.Close();
work.Join();
}
public void Dispose()
{
tasks.Enqueue(null);
wh.Close();
work.Join();
}
public void AddTask(string str)
{
lock(obj){tasks.Enqueue(str);wh.Set();}
}
}
class Program
{
static EventWaitHandle _waitHandle = new AutoResetEvent(false);
static void Main(string[] args)
{
ProducerConsumer prod = new ProducerConsumer();
prod.AddTask("Started");
for (int i = 0; i < 5; i++) prod.AddTask(Convert.ToString(i));
prod.AddTask("Done");
prod.ShutDown();
Console.ReadLine();
}
}
}
If I change the wh.WaitOne() to the else part then it starts working
public void doWork()
{
while (true)
{
string task = null;
lock (obj){
if (tasks.Count > 0)
{
task = tasks.Dequeue();
if (task == null) return;
else
{
Console.WriteLine("Performing task: " + task);
Thread.Sleep(1000); // simulate work...
}
}
else
wh.WaitOne();
}
}
}
Can somebody throw a light on why this is happening?

Cancel thread and restart it

When user resizes window some long text should be updated, but if the thread is already running it should be stopped and started over with new width parameter.
int myWidth;
private CancellationTokenSource tokenSource2 = new CancellationTokenSource();
private CancellationToken ct = new CancellationToken();
void container_Loaded(object sender, RoutedEventArgs e)
{
ct = tokenSource2.Token;
MyFunction();
}
void container_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (tokenSource2.Token.IsCancellationRequested)
MyFunction();
else
tokenSource2.Cancel();
}
void MyFunction()
{
myWidth = GetWidth();
Task.Factory.StartNew(() =>
{
string s;
for (int i=0;i<1000,i++){
s=s+Functionx(myWidth);
ct.ThrowIfCancellationRequested();
}
this.Dispatcher.BeginInvoke(new Action(() => {
ShowText(s);
}));
},tokenSource2.Token)
.ContinueWith(t => {
if (t.IsCanceled)
{
tokenSource2 = new CancellationTokenSource(); //reset token
MyFunction(); //restart
};
});
}
What now is happening is when I resize window I see text iteratively updating next several seconds as if old threads were not canceled. What am I doing wrong?
I don't think using global variables is a good idea in this case. Here's how I would do it by adding cancellation logic to my AsyncOp class from a related question. This code also implements the IProgress pattern and throttles the ViewModel updates.
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace Wpf_21611292
{
/// <summary>
/// Cancel and restarts an asynchronous operation
/// </summary>
public class AsyncOp<T>
{
readonly object _lock = new object();
Task<T> _pendingTask = null;
CancellationTokenSource _pendingCts = null;
public Task<T> CurrentTask
{
get { lock (_lock) return _pendingTask; }
}
public bool IsPending
{
get { lock (_lock) return _pendingTask != null && !_pendingTask.IsCompleted; }
}
public bool IsCancellationRequested
{
get { lock (_lock) return _pendingCts != null && _pendingCts.IsCancellationRequested; }
}
public void Cancel()
{
lock (_lock)
{
if (_pendingTask != null && !_pendingTask.IsCompleted && !_pendingCts.IsCancellationRequested)
_pendingCts.Cancel();
}
}
public Task<T> Run(
Func<CancellationToken, Task<T>> routine,
CancellationToken token = default,
bool startAsync = false,
bool continueAsync = false,
TaskScheduler taskScheduler = null)
{
Task<T> previousTask = null;
CancellationTokenSource previousCts = null;
Task<T> thisTask = null;
CancellationTokenSource thisCts = null;
async Task<T> routineWrapper()
{
// await the old task
if (previousTask != null)
{
if (!previousTask.IsCompleted && !previousCts.IsCancellationRequested)
{
previousCts.Cancel();
}
try
{
await previousTask;
}
catch (Exception ex)
{
if (!(previousTask.IsCanceled || ex is OperationCanceledException))
throw;
}
}
// run and await this task
return await routine(thisCts.Token);
};
Task<Task<T>> outerTask;
lock (_lock)
{
previousTask = _pendingTask;
previousCts = _pendingCts;
thisCts = CancellationTokenSource.CreateLinkedTokenSource(token);
outerTask = new Task<Task<T>>(
routineWrapper,
thisCts.Token,
continueAsync ?
TaskCreationOptions.RunContinuationsAsynchronously :
TaskCreationOptions.None);
thisTask = outerTask.Unwrap();
_pendingTask = thisTask;
_pendingCts = thisCts;
}
var scheduler = taskScheduler;
if (scheduler == null)
{
scheduler = SynchronizationContext.Current != null ?
TaskScheduler.FromCurrentSynchronizationContext() :
TaskScheduler.Default;
}
if (startAsync)
outerTask.Start(scheduler);
else
outerTask.RunSynchronously(scheduler);
return thisTask;
}
}
/// <summary>
/// ViewModel
/// </summary>
public class ViewModel : INotifyPropertyChanged
{
string _width;
string _text;
public string Width
{
get
{
return _width;
}
set
{
if (_width != value)
{
_width = value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Width)));
}
}
}
public string Text
{
get
{
return _text;
}
set
{
if (_text != value)
{
_text = value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
/// <summary>
/// MainWindow
/// </summary>
public partial class MainWindow : Window
{
ViewModel _model = new ViewModel { Text = "Starting..." };
AsyncOp<DBNull> _asyncOp = new AsyncOp<DBNull>();
CancellationTokenSource _workCts = new CancellationTokenSource();
public MainWindow()
{
InitializeComponent();
this.DataContext = _model;
this.Loaded += MainWindow_Loaded;
this.SizeChanged += MainWindow_SizeChanged;
}
void MainWindow_SizeChanged(object sender, SizeChangedEventArgs e)
{
_asyncOp.Run(WorkAsync, _workCts.Token);
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
_asyncOp.Run(WorkAsync, _workCts.Token);
}
async Task<DBNull> WorkAsync(CancellationToken token)
{
const int limit = 200000000;
var throttle = TimeSpan.FromMilliseconds(200);
// update ViewModel's Width
_model.Width = $"Width: {this.Width:#.##}";
// update ViewModel's Text using IProgress pattern
// and throttling updates
IProgress<int> progress = new Progress<int>(i =>
{
_model.Text = $"{(double)i / (limit - 1)* 100:0.}%";
});
var stopwatch = new Stopwatch();
stopwatch.Start();
// do some CPU-intensive work
await Task.Run(() =>
{
int i;
for (i = 0; i < limit; i++)
{
if (stopwatch.Elapsed > throttle)
{
progress.Report(i);
stopwatch.Restart();
}
if (token.IsCancellationRequested)
break;
}
progress.Report(i);
}, token);
return DBNull.Value;
}
}
}
XAML:
<Window x:Class="Wpf_21611292.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<TextBox Width="200" Height="30" Text="{Binding Path=Width}"/>
<TextBox Width="200" Height="30" Text="{Binding Path=Text}"/>
</StackPanel>
</Window>
It uses async/await, so if you target .NET 4.0, you'd need Microsoft.Bcl.Async and VS2012+. Alternatively, you can convert async/await to ContinueWith, which is a bit tedious, but always possible (that's more or less what the C# 5.0 compiler does behind the scene).
You should use Microsoft's Reactive Framework (aka Rx) - NuGet System.Reactive.Windows.Threading (for WPF) and add using System.Reactive.Linq; - then you can do this:
public MainWindow()
{
InitializeComponent();
_subscription =
Observable
.FromEventPattern<SizeChangedEventHandler, SizeChangedEventArgs>(
h => container.SizeChanged += h,
h => container.SizeChanged -= h)
.Select(e => GetWidth())
.Select(w => Observable.Start(
() => String.Concat(Enumerable.Range(0, 1000).Select(n => Functionx(w)))))
.Switch()
.ObserveOnDispatcher()
.Subscribe(t => ShowText(t));
}
private IDisposable _subscription = null;
That's all the code needed.
This responds to the SizeChanged event, calls GetWidth and then pushes the Functionx to another thread. It uses Switch() to always switch to the latest SizeChanged and then ignores any in-flight code. It pushes the result to the dispatcher and then calls ShowText.
If you need to close the form or stop the subscription running just call _subscription.Dispose().
Simple.

Categories