I'm trying to find some solutions to my problem here, but with no result (or I just do not get them right) so if anyone could help / explain i will be really gratefull.
I'm just developing a tool for system administrators using Win Form and now I need to create a continuous ping on the selected machine which is running on the background. There is an indicator for Online status on UI which I need to edit with background ping. So right now I'm in this state:
Class A (Win form):
ClassB activeRelation = new ClassB();
public void UpdateOnline(Relation pingedRelation)
{
//There is many Relations at one time, but form shows Info only for one...
if (activeRelation == pingedRelation)
{
if (p_Online.InvokeRequired)
{
p_Online.Invoke(new Action(() =>
p_Online.BackgroundImage = (pingedRelation.Online) ? Properties.Resources.Success : Properties.Resources.Failure
));
}
else
{
p_Online.BackgroundImage = (pingedRelation.Online) ? Properties.Resources.Success : Properties.Resources.Failure;
}
}
}
//Button for tunring On/Off the background ping for current machine
private void Btn_PingOnOff_Click(object sender, EventArgs e)
{
Button btn = (sender is Button) ? sender as Button : null;
if (btn != null)
{
if (activeRelation.PingRunning)
{
activeRelation.StopPing();
btn.Image = Properties.Resources.Switch_Off;
}
else
{
activeRelation.StartPing(UpdateOnline);
btn.Image = Properties.Resources.Switch_On;
}
}
}
Class B (class thats represent relation to some machine)
private ClassC pinger;
public void StartPing(Action<Relation> action)
{
pinger = new ClassC(this);
pinger.PingStatusUpdate += action;
pinger.Start();
}
public void StopPing()
{
if (pinger != null)
{
pinger.Stop();
pinger = null;
}
}
Class C (background ping class)
private bool running = false;
private ClassB classb;
private Task ping;
private CancellationTokenSource tokenSource;
public event Action<ClassB> PingStatusUpdate;
public ClassC(ClassB classB)
{
this.classB = classB;
}
public void Start()
{
tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
ping = PingAction(token);
running = true;
}
public void Stop()
{
if (running)
{
tokenSource.Cancel();
ping.Wait(); //And there is a problem -> DeadLock
ping.Dispose();
tokenSource.Dispose();
}
running = false;
}
private async Task PingAction(CancellationToken ct)
{
bool previousResult = RemoteTasks.Ping(classB.Name);
PingStatusUpdate?.Invoke(classB);
while (!ct.IsCancellationRequested)
{
await Task.Delay(pingInterval);
bool newResult = RemoteTasks.Ping(classB.Name);
if (newResult != previousResult)
{
previousResult = newResult;
PingStatusUpdate?.Invoke(classB);
}
}
}
So the problem is in deadlock when I cancel token and Wait() for task to complete -> it's still running, but While(...) in task is finished right.
You have a deadlock because ping.Wait(); blocks UI thread.
You should wait for task asynchronously using await.
So, if Stop() is event handler then change it to:
public async void Stop() // async added here
{
if (running)
{
tokenSource.Cancel();
await ping; // await here
ping.Dispose();
tokenSource.Dispose();
}
running = false;
}
If it is not:
public async Task Stop() // async added here, void changed to Task
{
if (running)
{
tokenSource.Cancel();
await ping; // await here
ping.Dispose();
tokenSource.Dispose();
}
running = false;
}
As mentioned by #JohnB async methods should have Async suffix so, the method should be named as StopAsync().
Similar problem and solution are explained here - Do Not Block On Async Code
You should avoid synchronous waiting on tasks, so you should always use await with tasks instead of Wait() or Result. Also, as pointed by #Fildor you should use async-await all the way to avoid such situations.
Related
I am creating a WPF app where I want to have a global bool im assuming, on the first button click I’ll set this bool to true and I want it to run a task (continuously call an API method) until I click the button again and it stops it. What would be the best way to do this?
private bool running = false;
private async void BtnTrade1_Buy_Click(object sender, RoutedEventArgs e)
{
if (!running)
{
running = true;
}
else
running = false;
if (running)
{
RunningNrunnin(running);
//tradeClient.GetTradeHistory();
}
}
public void RunningNrunnin(bool running)
{
if (running)
{
Task task = new Task(() =>
{
while (running)
{
GetTradeHistory();
Thread.Sleep(2000);
}
});
task.Start();
}
}
Added Below
I would like to call a method over and over until the user creates a cancel request on a thread in the background. I currently had it so I can call a action (a counter) and update the GUI each second but when I try to do this same thing with a method call it executes only once.
// Here is the method I want to call continously until canceled
private async void HistoryTest()
{
cancellationToken = new CancellationTokenSource();
task = Task.Factory.StartNew(async () =>
{
while (true)
{
cancellationToken.Token.ThrowIfCancellationRequested();
await Client2.GetHistory();
await Task.Delay(2000);
}
}, cancellationToken.Token);
}
public async Task GetHistory()
{
try
{
var response = await Client.Service.GetDataAsync
(
ProductType.BtcUsd,
5,
1
);
}
catch(Exception)
{
throw;
}
}
I made a little console test app to test this so I had to change the method signatures (static) and can't use ButtonClick on a console. I simulated the button click by putting as sleep between the programatic "button click".
This might get you started.
private static bool isRunning = false;
private static int clickCounter = 0;
private static int iterationsCounter = 0;
static void Main(string[] args)
{
Console.WriteLine(“Start”);
for(int i = 0; i < 7; i++)
{
BtnTrade1_Buy_Click();
System.Threading.Thread.Sleep(1000);
}
Console.WriteLine(“END”);
}
private static async Task BtnTrade1_Buy_Click()
{
iterationsCounter = 0;
isRunning = !isRunning;
Console.WriteLine($"Ha: {isRunning} {clickCounter++}");
await RunningNrunnin();
}
private static async Task RunningNrunnin()
{
await Task.Run(() => Runit());
}
private static void Runit()
{
while (isRunning)
{
GetTradeHistory();
System.Threading.Thread.Sleep(100);
}
}
private static void GetTradeHistory()
{
Console.WriteLine($"Hello Test {iterationsCounter++}");
}
Of course you wouldn't need all the counters and the Console.WriteLine() stuff. They are there to allow you to visualize what is happening.
Let me know if you need more info.
You don't need to do anything else inside the BtnTrade1_Buy_Click event handler, beyond toggling the isRunning field:
private bool _isRunning;
private void BtnTrade1_Buy_Click(object sender, RoutedEventArgs e)
{
_isRunning = !_isRunning;
}
The Task that is getting the trade history in a loop, needs to be started only once. You could start it in the Window_Loaded event. Storing the Task in a private field is a good idea, in case you decide to await it at some point, but if you are handling the exceptions inside the task it's not necessary.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
_ = StartTradeHistoryLoopAsync(); // Fire and forget
}
private async Task StartTradeHistoryLoopAsync()
{
while (true)
{
var delayTask = Task.Delay(2000);
if (_isRunning)
{
try
{
await Task.Run(() => GetTradeHistory()); // Run in the ThreadPool
//GetTradeHistory(); // Alternative: Run in the UI thread
}
catch (Exception ex)
{
// Handle the exception
}
}
await delayTask;
}
}
Don't forget to stop the task when the window is closed.
private void Window_Closed(object sender, EventArgs e)
{
_isRunning = false;
}
This will stop the calls to GetTradeHistory(), but will not stop the loop. You may need to add one more private bool field to control the loop itself:
while (_alive) // Instead of while (true)
I have a console app that instantiates a WeatherClientManager class.
The main thread in the console app requests current weather status in the WeatherClientManager class, but the WeatherClientManager class continuously receives data from a server.
In code:
public static void Main(string [])
{
Program p = new Program();
Task.Run(()=>p.RunLoop());
}
class Program{
WeatherClientManager wcM;
public void RunLoop()
{
wcM = new WeatherClientManager ();
await wcM.InitiateConnection().ConfigureAwait(false);
}
}
class WeatherClientManager
{
public async Task<bool> InitiateConnection()
{
TCPClient tcpClient = new TcpClient(GetTCPDetailsFromConfig())
await tcpClient.ConnectAsync()
CancellationTokenSource cts = new CancellationTokenSource();
if(tcpClient.Connected)
{
Task.Run(()=>ReceiveTask(cts.Token));
Task.Run(()=>SendKeepAlive(cts.Token));
return true;
}
return false;
}
private void ReceiveTask(CancellationToken t)
{
try{
networkStream.Receive(..) // throws exception
}
catch(Exception e)
{
Stop(e);
}
}
private void SendKeepAlive(CancellationToken t)
{
while(!t.IsCancellationRequested)
{
try{
networkStream.Write(..) // throws exception
}
catch(Exception e)
{
Stop(e);
}
}
}
private void Stop(Exception e )
{
log.Error(e);
e.Cancel();
}
}
One of many crap ideas I have is:
Task.Run( () =>
{
while(true)
{
var t1 = Task.Run(()=>ReceiveTask(cts.Token));
var t2= Task.Run(()=>SendKeepAlive(cts.Token));
try{
Tasks.WhenAny(); // should block
}
catch(Exception e)
{
}
finally{
Cleanup();
InitiateConnections();
}
}
}
But I hate the idea of spinning a task to control two sub tasks. My problem is where and how to re-initiate the connection. Any ideas?
EDIT:
I've updated the code such that WeatherClientManager has a OnDisconnectDetected event. So the Program.cs class subscribes like so:
weatherServerManager.OnDisconnectDetected += HandleDisconnectDetection
public async void HandleDisconnectDetection()
{
wsM = new WeatherClientManager ();
wsM.InitiateConnection().ConfigureAwait(false);
}
private void SendKeepAlive(CancellationToken t)
{
while (...)
{
try{}
catch(Exception e)
{
OnDisconnectDetected?.Invoke();
}
}
}
When the handler is invoked by the WeatherClientManager it creates a new task that should continue in a different context. The KeepAlive task should exit then.
Still feels hacky but ideas welcome!
As a general rule, I prefer composition of methods over raising events. In particular, avoid the Task.Run-based fire-and-forget.
In the case of asynchronous sockets, I think it makes sense to give each socket a main loop:
class WeatherClientManager
{
public async Task MainLoop()
{
TCPClient tcpClient = new TcpClient(GetTCPDetailsFromConfig())
await tcpClient.ConnectAsync();
CancellationTokenSource cts = new CancellationTokenSource();
var receiveTask = Task.Run(()=>ReceiveTask(cts.Token));
var keepaliveTask = Task.Run(()=>SendKeepAlive(cts.Token));
await Task.WhenAll(receiveTask, keepaliveTask);
}
}
These can then be composed into the main program's main loop:
class Program
{
public async Task RunLoop()
{
while (true)
{
wcM = new WeatherClientManager();
await wcM.MainLoop();
}
}
}
which in turn is composed into Main:
public static void Main(string [])
{
Program p = new Program();
p.RunLoop().GetAwaiter().GetResult();
}
By avoiding fire-and-forget, you're ensuring that your code will always observe all exceptions. Ignoring tasks is occasionally okay but usually a mistake.
I need to implement a periodic timer (10 seconds), that will check for some changes and update Database and UI. I need to wait until "Job" is finished and then start timer again. I call
timer.Cancel()
on "Job" starting and then create timer again on finish. But seems when I use work-item handlers that use the async keyword ,the thread pool work item may be set to the complete state before all of the code in the handler has executed.
See full code :
private bool IsStop = false;
private async Task DoWork()
{
try
{
if (IsStop)
{
Stop();
return;
}
timer.Cancel();
var resultIds = await DB.getUnresolvedData();
if (resultIds.IsNullOrEmpty())
{
return;
}
var json = await server.RequestSTatus(resultIds);
if (string.IsNullOrEmpty(json))
return;
Model result = JsonConvert.DeserializeObject<Model>(json);
UpdateDB(result);
}
catch (Exception ex)
{
}
finally
{
Start();
}
}
public void Start()
{
if (IsStop) return;
timer = ThreadPoolTimer.CreatePeriodicTimer(async (t) =>
{
await DoWork();
}, TimeSpan.FromSeconds(10));
}
The question is ,how I can wait until "Job" is finished and then start again ?
You can do it little bit different. but code will be more clear and you will use ThreadPool anyway. Just use Task.Delay for timer. Something like this.
private bool isRunning;
public async void Start()
{
if (isRunning)
return;
isRunning = true;
while (isRunning)
{
await DoWork();
//wait period.
await Task.Delay(TimeSpan.FromSeconds(10));
if (!isRunning)
return;
}
}
private async Task DoWork()
{
//Do your work here.
}
public void Stop()
{
isRunning = false;
}
It is just a general idea.
I am working on xamarin forms PCL + iOS. I want to cancel a task when it enters background. And start it all over when app enters foreground.
This is what I have tried so far.. I am not sure if my way cancels any task or what is it that is happening here?
async void getData()
{
bool isSuccess = await getSomeData();
if(isSuccess)
await getSomeMoreData();
}
CancellationTokenSource cts;
async Task<bool> getSomeData()
{
cts = new CancellationTokenSource();
AppEntersBackgorund += (sender,args) => { cts. cancel();});
CancellationToken token = new CancellationToken();
token = cts.token;
await Task.Run(() => {
token.ThrowIfCancellationRequested();
isSuccess = ParserData(token); // parsedata also checks periodically if task is cancelled
},token); //what happens here when cancel called?
return isSuccess;
}
async void getSomeMoreData()
{
if(!cts.IsCancellationRequested)
cts = new CancellationTokenSource();
AppEntersBackgorund += (sender,args) => { cts. cancel();});
CancellationToken token = new CancellationToken();
token = cts.token;
await Task.Run(() =>
{
token.ThrowIfCancellationRequested();
ParseSomeMoreData(token);
},token);
}
When app enters foregorund, I again call the getData() method so that i start all over again.
What happens is that, Task is not getting cancelled, rather getSomeMoreData is getting called twice ( or the no. of times the app goes from background to foreground) .
Can someone explain how I can achieve this? And what is happening here?
Actually, this is not a Xamarin problem, it is just a C# problem except the app's enter foreground/background events.
For the requirements you need, you should make a task manager object to implement it.
I wrote a sample code for you:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Threading;
namespace BackGroundTask
{
public class TaskManager
{
//The instance property
private static TaskManager instance;
public static TaskManager Instance{
get{
if(null == instance)
instance = new TaskManager();
return instance;
}
}
private bool actionTaskFreeFlag = true;//Flag for if actionTask is available or not
private Queue<Action> taskQueue;//A queue to collect the tasks you added into the manager
private Task scanTask;//A Task to sacn the queue
private Task actionTask;//A task to do the current action
private Thread actionTaskRunningThread;//Record the thread that current action is working on
public TaskManager()
{
taskQueue = new Queue<Action>();
scanTask = new Task(() =>
{
while (true)
{
if (actionTaskFreeFlag && taskQueue.Count > 0)//If there still something to do and the actionTask is available then do the action
{
actionTaskFreeFlag = false;
Action action = taskQueue.Dequeue();
actionTask = new Task(() => {
actionTaskRunningThread = System.Threading.Thread.CurrentThread;
action();
});
actionTask.Start();
actionTask.ContinueWith(delegate {
actionTaskFreeFlag = true;
});
}
}
});
scanTask.Start();
}
public void AddAction(Action action)
{
taskQueue.Enqueue(action);
}
public void CancelCurrentTaskAndClearTaskQueue()
{
Console.WriteLine("CancelCurrentTaskAndClearTaskQueue");
if(null != actionTaskRunningThread)
actionTaskRunningThread.Abort();
taskQueue.Clear();
}
}
}
And this is a sample code for how to use it to do the stuff you want:
//App enter background event
AppDelegate.Instance.AppDidEnterBackground += delegate {
TaskManager.Instance.CancelCurrentTaskAndClearTaskQueue();
};
//App enter forcenground event
AppDelegate.Instance.AppWillEnterForeground += delegate {
if (AppDelegate.FlagForGetData)
{
TaskManager.Instance.AddAction(GetData);
TaskManager.Instance.AddAction(GetMoreData);
}
};
And this is the methods for testing:
private void GetData()
{
AppDelegate.FlagForGetData = true;
Console.WriteLine("Began getting data.");
System.Threading.Thread.Sleep(5000);
AppDelegate.FlagForGetData = false;
Console.WriteLine("Getting data succeed.");
}
private void GetMoreData()
{
Console.WriteLine("Began getting more data.");
System.Threading.Thread.Sleep(3000);
Console.WriteLine("Getting more data succeed.");
}
Hope it can help you.
I use awesomium to automate a site. I tried to use asynchronous programming, because I didn't want my GUI to freeze, but I have a problem at a event (a popup appear and I want to make some actions in this popup until I close it) when the application doesn't continue how I want. After the event is fired I want my application to continue with the event method (webc_ShowCreatedWebView and after with popupTwitter(method), but I find that when executing JavaScript code, the control returns in the While from the first method. How could I do that after the earnpoints method is called and the events is fired to finish the event and the method and after that the control return in the while.
private async void button4_Click(object sender, EventArgs e)
{
Twitter twitter = new Twitter(webView);
twitter.Login(webView);
webView.ShowCreatedWebView += webc_ShowCreatedWebView;
addmefast.Login(webView);
int i = 0;
while (i < 10)
{
Task earnpoints = EarnPoints(webView);
await earnpoints;
//Here i don't want to continue until EarnPoints method > webc_ShowCreatedWebView event > popupTwitter method it's finished.
i++;
}
}
public async Task EarnPoints(IWebView web)
{
web.Source = "http://addmefast.com/free_points/twitter".ToUri();
await Task.Delay(3000);
web.ExecuteJavascript("document.getElementsByClassName('single_like_button btn3-wrap')[0].click();"); //event fired: webc_ShowCreatedWebView
}
async void webc_ShowCreatedWebView(object sender, ShowCreatedWebViewEventArgs e)
{
WebView view = new WebView(e.NewViewInstance);
await popupTwitter(view);
}
async Task popupTwitter(WebView view)
{
Popupform FormTwitter = new Popupform(view);
FormTwitter.Show();
await Task.Delay(6000);
FormTwitter.Twitter();
await Task.Delay(2000);
FormTwitter.Close();
await Task.Delay(4000);
}
I also had problems implementing async methods with awesomium, but got it working.
First I made this wrapper. Have to be created on the main thread.
public class AsyncWebView
{
public static SynchronizationContext _synchronizationContext;
private readonly WebView _webView;
public AsyncWebView()
{
_synchronizationContext = SynchronizationContext.Current;
_webView = WebCore.CreateWebView(1024, 900);
}
public async Task Navigate(String url)
{
Debug.WriteLine("Navigating");
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
FrameEventHandler handler = (sender, args) =>
{
Debug.WriteLine(args.Url);
if (!_webView.IsNavigating && !_webView.IsLoading)
tcs.SetResult(true);
};
_webView.LoadingFrameComplete += handler;
_synchronizationContext.Send(SetWebViewSource, url);
await tcs.Task;
_webView.LoadingFrameComplete -= handler;
Debug.WriteLine("Done");
}
private void SetWebViewSource(object url)
{
_webView.Source = new Uri((string)url);
}
}
Usage:
async Task test()
{
await webView.Navigate("http://www.nytimes.com");
Debug.WriteLine("All done");
}
Just make sure you have a SynchronizationContext where the AsyncWebView constructor is called from.