The Issue: After using HttpClient correctly (as a static single instance) over a long time, ConcurrentStack+Node instances consistently grow in size causing increased memory usage. Edit: This seems related to the dispatcher called in SetText and AddText.
The Use-Case: I am polling a device on my local network through HTTP transmission. I use System.Threading.Timer to control the frequency of my requests. The callback for the polling timer is async void so that the await operator can be used for continuation control. The default window dispatcher is used to display text results after the HttpRequest is called.
The Code for Recreation: .NET Framework 4.5 ; WPF ; All standard settings with a single TextBlock created in MainWindow with x:Name="Debug"
using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace LeakIsolator
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
static MainWindow()
{
WebClient.CachePolicy = new System.Net.Cache.RequestCachePolicy(System.Net.Cache.RequestCacheLevel.NoCacheNoStore);
HttpClient.DefaultRequestHeaders.CacheControl = new System.Net.Http.Headers.CacheControlHeaderValue()
{
NoStore = true,
NoCache = true
};
}
public MainWindow()
{
InitializeComponent();
Timer = new Timer(Poll, null, Timeout.Infinite, Timeout.Infinite);
Loaded += MainWindow_Loaded;
}
private static HttpClient HttpClient { get; } = new HttpClient();
private static WebClient WebClient { get; } = new WebClient();
private Timer Timer { get; }
private void SetText(string text)
{
Dispatcher.Invoke(() => Debug.Text = text);
}
private void AddText(string text)
{
Dispatcher.Invoke(() => Debug.Text += text);
}
private async void Poll(object a)
{
try
{
SetText("Getting status...");
SetText(await GetResponseStringHttpClient("http://10.10.10.87/status"));
}
catch (Exception e)
{
SetText($"Exception. {e.Message}");
}
finally
{
Start();
}
}
private async Task<string> GetResponseStringWebClient(string address)
{
return await WebClient.DownloadStringTaskAsync(address);
}
private async Task<string> GetResponseStringHttpClient(string address)
{
using (var response = await HttpClient.GetAsync(address))
{
if (response.IsSuccessStatusCode)
{
AddText(" Success...");
return await response.Content.ReadAsStringAsync();
}
else
{
AddText(" Fail.");
return null;
}
}
}
private void Start()
{
Timer.Change(1000, Timeout.Infinite);
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Start();
}
}
}
Using ANTS memory profiler, and running the application for 37 minutes in the most recent test, 2.618 MB (small for now, but this is a long-running application) inaccessible-to-me bytes are allocated by a PinnableBufferCache, presumably stored in the gen 2 heap.
I am not sure if this is a memory leak or is a part of something I do not understand. Why am I seeing these memory increases and why am I unable to dictate the local caching behavior of HttpClient and WebClient?
I would guess AddText, you could try commenting out the code in that method and see if the memory leak goes away.
Related
I have this example as a server.
The question that only works well for me on console.
I want to pass it to windows Form. And I don't know how to apply it.
Because I understand that it is bad practice from another class such as creating a Form1 Method and using a Form1 object in the Server class.
As if in the Server class I call the textbox or things like that.
The question that I think I would have to adapt all the code back for windows Form?
Or stop using the classes and use the typical TcpClient, TpcListener as in the videos that declare it at the moment in Form1.
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace Chatroom
{
delegate void MessageEventHandler(object sender, MessageEventArgs e);
class MessageEventArgs : EventArgs
{
public string Message { get; private set; }
public MessageEventArgs(string message)
{
this.Message = message;
}
}
class Server
{
private TcpListener serverSocket;
private List<Worker> workers = new List<Worker>();
public Server(int port)
{
//serverSocket = new TcpListener(port);// deprecated
// the same way
serverSocket = new TcpListener(IPAddress.Any, port);
serverSocket.Start();
}
private void WaitForConnection()
{
while (true)
{
TcpClient socket = serverSocket.AcceptTcpClient();
Worker worker = new Worker(socket);
AddWorker(worker);
worker.Start();
}
}
private void Worker_MessageReceived(object sender, MessageEventArgs e)
{
BroadcastMessage(sender as Worker, e.Message);
}
private void Worker_Disconnected(object sender, EventArgs e)
{
RemoveWorker(sender as Worker);
}
private void AddWorker(Worker worker)
{
lock (this)
{
workers.Add(worker);
worker.Disconnected += Worker_Disconnected;
worker.MessageReceived += Worker_MessageReceived;
}
}
private void RemoveWorker(Worker worker)
{
lock (this)
{
worker.Disconnected -= Worker_Disconnected;
worker.MessageReceived -= Worker_MessageReceived;
workers.Remove(worker);
worker.Close();
}
}
private void BroadcastMessage(Worker from, String message)
{
lock (this)
{
message = string.Format("{0}: {1}", from.Username, message);
for (int i = 0; i < workers.Count; i++)
{
Worker worker = workers[i];
if (!worker.Equals(from))
{
try
{
worker.Send(message);
}
catch (Exception)
{
workers.RemoveAt(i--);
worker.Close();
}
}
}
}
}
class Worker
{
public event MessageEventHandler MessageReceived;
public event EventHandler Disconnected;
private readonly TcpClient socket;
private readonly Stream stream;
public string Username { get; private set; } = null;
public Worker(TcpClient socket)
{
this.socket = socket;
this.stream = socket.GetStream();
}
public void Send(string message)
{
byte[] buffer = Encoding.UTF8.GetBytes(message);
stream.Write(buffer, 0, buffer.Length);
}
public void Start()
{
new Thread(Run).Start();
}
private void Run()
{
byte[] buffer = new byte[2018];
try
{
while (true)
{
int receivedBytes = stream.Read(buffer, 0, buffer.Length);
if (receivedBytes < 1)
break;
string message = Encoding.UTF8.GetString(buffer, 0, receivedBytes);
if (Username == null)
Username = message;
else
MessageReceived?.Invoke(this, new MessageEventArgs(message));
}
}
catch (IOException) { }
catch (ObjectDisposedException) { }
Disconnected?.Invoke(this, EventArgs.Empty);
}
public void Close()
{
socket.Close();
}
}
static void Main(string[] args)
{
try
{
Server server = new Server(3393);
server.WaitForConnection();
}
catch (IOException) { }
}
}
}
The problem is this. If I have Form1.
As I relate it, as I do eg. Every time a new Client is created it is added by a ListBox from the Server class. In theory you can't or if you can or is it bad practice?
Class Server{
private void RemoveWorker(Worker worker)
{
lock (this)
{
**textbox.text +="Cliente desconectado";**
worker.Disconnected -= Worker_Disconnected;
worker.MessageReceived -= Worker_MessageReceived;
workers.Remove(worker);
worker.Close();
}
}
}
How could it be done without being in the main WinForm class?
Here are steps to help you start.
Create a new WinForms project in VisualStudio.
You project should build and show the form right away without you having to do anything.
You should have Program.cs that contains the Main() method. You do not need to change it. This is what causes the Form1 to load and display.
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
You can right-click on the Form1.cs and select View Code to see the code behind page.
There you will have the constructor that has InitializeComponent() method that creates all of your GUI "stuff".
Add a listener to run when the Form loads. This is where you can add your server stuff.
Open your GUI designer, go to Form1->Properties and add a new function to the Load
This is where you will write your code. For example, you can create and start the server.
private void Form1_Load(object sender, EventArgs e)
{
try
{
Server server = new Server(3393);
server.WaitForConnection();
}
catch (IOException) { // Put something here like a log }
}
Your server can go to a new Class that can be in a new file like Server.cs. Just make sure that WaitForConnection() is public.
This should get you started. When you run into an issue, just create a new question on SO and make sure to add your latest code.
Some suggestions:
Use a delegate to communicate between Server and the GUI
You may want to run the Server in another thread. Test it first to get it working and see if this is what you need/want
You don't normally want to run a server as WinForms project. If you accidently close the form, you kill your server.
Make sure to have a Form1_Close event and shut down your server there correctly
I have a Winform application that needs to manage several state machines concurrently. Each state machine may have a socket, serial port or other external connection.
Here is my state machine shell implemented using async/await and dataflow BlockBuffer as a message queue.
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using System.Windows.Forms;
public class StateMachineMessage
{
private static int id = 1;
public int Id { get; } = 0;
public string Name { get; }
public string Text { get; }
public BufferBlock<StateMachineMessage> Queue { get; set; }
public StateMachineMessage(string name, string text)
{
Id = Interlocked.Increment(ref id);
Name = name;
Text = text;
Queue = null;
}
public StateMachineMessage(string name, string text, BufferBlock<StateMachineMessage> queue)
{
Id = Interlocked.Increment(ref id);
Name = name;
Text = text;
Queue = queue;
}
public override string ToString()
{
return "(" + Id + ":" + Name + ":" + Text + ")";
}
}
public class StateMachine
{
private int State { get; }
public string Name { get; }
RichTextBox TextBox { get; }
BufferBlock<StateMachineMessage> Queue { get; }
public StateMachine(string name, BufferBlock<StateMachineMessage> queue, RichTextBox textBox)
{
Name = name;
Queue = queue;
TextBox = textBox;
}
public async Task StartAsync()
{
while (await Queue.OutputAvailableAsync())
{
var request = await Queue.ReceiveAsync();
TextBox.AppendText(string.Format("{0}: {1}: {2}\n",
Thread.CurrentThread.ManagedThreadId,
Name, request));
if (request.Queue != null)
{
var response = new StateMachineMessage("RESPONSE", "Good Morning!");
TextBox.AppendText(string.Format("{0}: {1}: {2}\n",
Thread.CurrentThread.ManagedThreadId,
Name, response));
await request.Queue.SendAsync(response);
}
}
}
}
The main form creates the dataflow block buffers and state machines on initialization.
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using System.Windows.Forms;
namespace DataflowThread
{
public partial class Form1 : Form
{
// Enables the user interface to signal cancellation.
CancellationTokenSource cancellationSource;
// The worker state machine and dataflow node
StateMachine workerMachine;
BufferBlock<StateMachineMessage> workQueue;
// The main thread state machine
StateMachine mainMachine;
BufferBlock<StateMachineMessage> mainQueue;
// Enables progress bar actions to run on the UI thread.
TaskScheduler uiTaskScheduler;
public Form1()
{
InitializeComponent();
InitializeDataflow();
StartStateMachines();
}
public void InitializeDataflow()
{
// Create the cancellation source.
cancellationSource = new CancellationTokenSource();
// Create the UI task scheduler from the current sychronization context.
uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
// Create dataflow options
var options = new ExecutionDataflowBlockOptions();
options.CancellationToken = cancellationSource.Token;
options.TaskScheduler = uiTaskScheduler;
// Create dataflow nodes for the main thread and worker state machines
mainQueue = new BufferBlock<StateMachineMessage>(options);
workQueue = new BufferBlock<StateMachineMessage>(options);
// Create the state machines for the worker and main thread
mainMachine = new StateMachine("MainMach", mainQueue, richTextBox1);
workerMachine = new StateMachine("WorkerMach", workQueue, richTextBox2);
}
public void StartStateMachines()
{
var mainTask = mainMachine.StartAsync();
var workerTask = workerMachine.StartAsync();
}
~Form1()
{
cancellationSource.Dispose();
}
private void sendButton_Click(object sender, EventArgs e)
{
var request = new StateMachineMessage("REQUEST", "Hello World!", mainQueue);
richTextBox1.AppendText(string.Format("{0}: {1}: {2}\n",
Thread.CurrentThread.ManagedThreadId,
mainMachine.Name, request));
workQueue.Post(request);
}
private async void cancelButton_Click(object sender, EventArgs e)
{
try
{
workQueue.Complete();
mainQueue.Complete();
await Task.WhenAll(workQueue.Completion, mainQueue.Completion);
richTextBox1.AppendText("All queues Completed\n");
resetButton.Enabled = true;
}
catch (OperationCanceledException ex)
{
richTextBox1.AppendText(ex.ToString());
}
}
private void sendTenButton_Click(object sender, EventArgs e)
{
for (int i = 0; i < 10; i ++)
{
var request = new StateMachineMessage("REQUEST", "Hello World!", mainQueue);
richTextBox1.AppendText(string.Format("{0}: {1}: {2}\n",
Thread.CurrentThread.ManagedThreadId,
mainMachine.Name, request));
workQueue.Post(request);
}
}
private void resetButton_Click(object sender, EventArgs e)
{
resetButton.Enabled = false;
InitializeDataflow();
StartStateMachines();
richTextBox1.Clear();
richTextBox2.Clear();
}
}
}
One started each state machine loops on the BlockBuffer until cancelled. My question:
What is the correct async/await usage for the outermost StartStateMachines() method?
Silently ignoring the Tasks returned from each state machine start feels funny. I am relatively new to C# and am trying to use the async / await patterns where I would normally use a message queue and thread. I am also trying to be a bit modern and not use Invoke/BeginInvoke everywhere. Any help is appreciated.
Thanks in advance.
Sean
EDIT - I added the full state machine source and Program.cs here:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace DataflowThread
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}
I have a project where I am using System.Net.Http.HttpClient. I am trying to centralize all calls to my Web APIs so that I have common error handing etc. I created the following class in my project.
using ModernHttpClient;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
namespace WebAPIHelper
{
class WebAPICaller
{
public async Task<string> CallWebService(string ps_URI)
{
HttpClient lobj_HTTPClient = null;
HttpResponseMessage lobj_HTTPResponse = null;
string ls_Response = "";
//We assume the internet is available.
try
{
//Get the Days of the Week
lobj_HTTPClient = new HttpClient(new NativeMessageHandler());
lobj_HTTPClient.BaseAddress = new Uri(App.APIPrefix);
lobj_HTTPClient.DefaultRequestHeaders.Accept.Clear();
lobj_HTTPClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
lobj_HTTPResponse = await lobj_HTTPClient.GetAsync(ps_URI);
if (!lobj_HTTPResponse.IsSuccessStatusCode)
{
Debug.WriteLine(lobj_HTTPResponse.ReasonPhrase);
}
else
{
ls_Response = await lobj_HTTPResponse.Content.ReadAsStringAsync();
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
finally
{
if (lobj_HTTPClient != null)
lobj_HTTPClient.Dispose();
if (lobj_HTTPResponse != null)
{
lobj_HTTPResponse.Dispose();
}
}
return ls_Response;
}
}
}
I call the function from an instance object I created in my ViewModel class for languages as follows:
using ModernHttpClient;
using Newtonsoft.Json;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
namespace WebAPIHelper
{
public class VM_Languages : INotifyPropertyChanged
{
/// <summary>
/// A collection for CGSLanguage objects.
/// </summary>
public ObservableCollection<GBSLanguage_ForList> Items_ForList { get; private set; }
const string ic_LanguagesAPIUrl = #"/languages/true";
/// <summary>
/// Constructor for the Languages view model.
/// </summary>
public VM_Languages()
{
this.Items_ForList = new ObservableCollection<GBSLanguage_ForList>();
}
/// <summary>
/// Indicates of the view model data has been loaded
/// </summary>
public bool IsDataLoaded
{
get;
private set;
}
/// <summary>
/// Creates and adds a the countries to the collection.
/// </summary>
public async Task LoadData()
{
HttpClient lobj_HTTPClient = null;
HttpResponseMessage lobj_HTTPResponse = null;
string ls_Response = "";
try
{
IsDataLoaded = false;
string ls_WorkLanguageURI = ic_LanguagesAPIUrl;
//Get the Days of the Week
lobj_HTTPClient = new HttpClient(new NativeMessageHandler());
lobj_HTTPClient.BaseAddress = new Uri(App.APIPrefix);
lobj_HTTPClient.DefaultRequestHeaders.Accept.Clear();
lobj_HTTPClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
**//This call will not work
//WebAPICaller lobj_APICaller = new WebAPICaller();
//ls_Response = lobj_APICaller.CallWebService(ls_WorkLanguageURI).Result;
//This call will work
lobj_HTTPResponse = await lobj_HTTPClient.GetAsync(ls_WorkLanguageURI);**
if (lobj_HTTPResponse.IsSuccessStatusCode)
{
if (this.Items_ForList != null)
this.Items_ForList.Clear();
ls_Response = await lobj_HTTPResponse.Content.ReadAsStringAsync();
Items_ForList = JsonConvert.DeserializeObject<ObservableCollection<GBSLanguage_ForList>>(ls_Response);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
finally
{
this.IsDataLoaded = true;
NotifyPropertyChanged("GBSLanguages_ForList");
}
}
/// <summary>
/// Notifies subscribers that a property has changed.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I have all my ViewModels in a static class so I only ever get one instance of them as follows:
namespace WebAPIHelper
{
public static class ViewModelObjects
{
private static VM_Languages iobj_Languages;
public static VM_Languages Languages
{
get
{
if (iobj_Languages == null)
iobj_Languages = new VM_Languages();
return iobj_Languages;
}
}
}
}
In my on appearing code of my main page I have the following call to retrieve the data from the WebAPI
protected override async void OnAppearing()
{
Device.BeginInvokeOnMainThread(() =>
{
if (!ViewModelObjects.Languages.IsDataLoaded)
ViewModelObjects.Languages.LoadData();
});
//If the DOW and Language data are not loaded yet - wait
while (!ViewModelObjects.Languages.IsDataLoaded)
{
await Task.Delay(1000);
}
}
The problem is when I attempt to use my WebAPICaller class, it appears to crash on the line. I never get a return from it. No exceptions are ever raised and my program never continues.
lobj_HTTPResponse = await lobj_HTTPClient.GetAsync(ps_URI);
If I make what I believe to be the exact same call from my ViewModel, it works. (I have both the call to the WebAPICaller class as well as a direct call to GetAsync in the View Model so you can test it out by commenting and uncommenting.)
Any idea as to what I am doing wrong?
Link to full sample project:
https://1drv.ms/u/s!Ams6cZUzaeQy3M8uGAuaGggMt0Fi-A
So here is what I found. It seems that awaiting the HTTPClient.GetAsync was causing the error. (Pretty sure the thread was blocked.) So to start with I took out the await and added code to delay the task when HTTPClient's return task was not completed.
var lobj_Result = lobj_HTTPClient.GetAsync(ps_URI);
while (!lobj_Result.IsCompleted)
{
Task.Delay(100);
}
Because I no longer await the call in the LoadData method, I was able to remove the async Task declaration and simply make it a method.
public void LoadData()
{
HttpClient lobj_HTTPClient = null;
HttpResponseMessage lobj_HTTPResponse = null;
string ls_Response = "";
try
{
IsDataLoaded = false;
string ls_WorkLanguageURI = ic_LanguagesAPIUrl;
//Get the Days of the Week
lobj_HTTPClient = new HttpClient(new NativeMessageHandler());
lobj_HTTPClient.BaseAddress = new Uri(App.APIPrefix);
lobj_HTTPClient.DefaultRequestHeaders.Accept.Clear();
lobj_HTTPClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//This call will not work
WebAPICaller lobj_APICaller = new WebAPICaller();
ls_Response = lobj_APICaller.CallWebService(ls_WorkLanguageURI).Result;
if (ls_Response.Trim().Length > 0)
{
if (this.Items_ForList != null)
this.Items_ForList.Clear();
Items_ForList = JsonConvert.DeserializeObject<ObservableCollection<GBSLanguage_ForList>>(ls_Response);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
finally
{
this.IsDataLoaded = true;
NotifyPropertyChanged("GBSLanguages_ForList");
}
}
The I could remove the async from my on appearing event and simply call the loaddata event synchronously.
if (!ViewModelObjects.Languages.IsDataLoaded)
ViewModelObjects.Languages.LoadData();
After making all these changes, the desired result was achieved. Everything ran in synchronous manner (I know the call to GetAsync function of the HTTPClient is still async) and did not return until the results were retrieve and loaded into the object.
I hope this helps other people out. :)
There are a couple of things I see going on. First, the commented-out code:
//This call will not work
//WebAPICaller lobj_APICaller = new WebAPICaller();
//ls_Response = lobj_APICaller.CallWebService(ls_WorkLanguageURI).Result;
Is using .Result instead of await. This may be the root of your problem. Using .Result should be avoided in general, just use await. See the answer here Await on a completed task same as task.Result? for more details on why.
The second thing is that you are already on the UI thread when OnAppearing is called, so there is no need to call Device.BeginInvokeOnMainThread. What you're doing in that method currently is equivalent to:
protected override async void OnAppearing()
{
if (!ViewModelObjects.Languages.IsDataLoaded)
await ViewModelObjects.Languages.LoadData();
}
The next question is whether or not it's a great idea to be doing this in OnAppearing(). This can cause your app to seem non-responsive.
The general use of Device.BeginInvokeOnMainThread is for when you don't know if you're currently on the main thread, but these UI event handlers always are called on the main thread by the Xamarin platform.
Based on the source code on github
void IPageController.SendAppearing()
{
if (_hasAppeared)
return;
_hasAppeared = true;
if (IsBusy)
MessagingCenter.Send(this, BusySetSignalName, true);
OnAppearing();
EventHandler handler = Appearing;
if (handler != null)
handler(this, EventArgs.Empty);
var pageContainer = this as IPageContainer<Page>;
((IPageController)pageContainer?.CurrentPage)?.SendAppearing();
}
there is still a way to do this with async/await approach.
You will notice that the OnAppearing method is called just before the event is triggered.
Subscribe to the Appearing event of the page/view
protected override void OnAppearing() {
this.Appearing += Page_Appearing;
}
and create an async method like you did originally but this time have it on an actual even handler
private async void Page_Appearing(object sender, EventArgs e) {
if (!ViewModelObjects.Languages.IsDataLoaded)
await ViewModelObjects.Languages.LoadData();
//unsubscribing from the event
this.Appearing -= Page_Appearing;
}
This way there is no need to busy wait delay the thread while waiting for the task to complete.
Okay so I have a function called readSensor which you guessed it.. reads a sensor.
But the sensors usually take about 100ms to respond. So in the readSensor function I am basically just starting a timer.
On the timed event I read the serialport and get my response.
However this means that my response is in the onTimedEvent when I want it to be in the readSensor function..
Basically from the main form I want to be able to do this.
value = readSensor()
when at the minute all I can do is readSensor() and then I can see the response is coming back by displaying it in a messagebox once the timedEvent fires.
here is my code. (I have missed out loads of serialport setup and stuff but hopefully you can see the problem I am in)
I don't want to just wait in the function for 100ms though polling the timer as that will make my program slow..
I want to somehow get the response back to the readSensor function and then back to the form.
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;
using System.Timers;
namespace readSensor
{
public partial class readSens : UserControl
{
public readSens()
{
InitializeComponent();
}
private System.Timers.Timer rTimer;
SerialPort sp = new SerialPort();
private void setupTimer()
{
// Create a timer with a 100ms response.
rTimer = new System.Timers.Timer(100);
rTimer.SynchronizingObject = this;
// Hook up the Elapsed event for the timer.
rTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
}
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
string response = getResponse();
}
public string getResponse()
{
string status = "";
byte[] readBuffer = new byte[255];
if (sp.IsOpen)
{
if (sp.BytesToRead > 0) //there is data to read
{
int length = sp.BytesToRead;
for (int i = 0; i < length; i++)
{
readBuffer[i] = (byte)sp.ReadByte();
status = "pass";
return status;
}
}
}
public void readSensor(byte addr)
{
if (!sp.IsOpen)
{
openPort();
readSensor(addr); // calls itself again once port is opened
}
else if (sp.IsOpen)
{
rTimer.Start();
}
else
{
MessageBox.Show("Port not opened yet");
}
}
}
}
In the main form I am basically just saying
setupTimer();
readSensor();
on a button click.
I don't think you can do it without some callback mechanism. You could implement a while loop but that is not eficient as it would introduce spinning.
My advice is to implement a proper async pattern or something simple like:
ReadSensor(addr, DoSomethingWithResult);
public void DoSomethingWithResult(string result)
{
Console.WriteLine (result);
}
public partial class ReadSens : UserControl
{
private Action<string> _responseCallback;
public void ReadSensor(byte addr, Action<string> responseCallback)
{
_responseCallback = responseCallback;
// initiate timer
}
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
string response = getResponse();
_responseCallback(response);
}
}
Start a separate thread, then from that thread write into a queue the results back in your main thread.
class Game1
{
//We declare a queue, which is like an array that we can extract and enter data easily in a FIFO (first in, first out) style list.
Queue<string> q = new Queue<string>();
public void threadStart(object obj)
{
//We get the result of your function, while our main function is still looping and waiting.
string result = readInput()
//We tell C# that the parameter we passed in, is in fact the Game1 class passed from "t.Start"
Game1 game = (Game1)obj;
//This puts our "result" into the queue.
game.q.Enqueue(result);
}
public void start()
{
//Declares a new thread, which will run "threadStart" function.
System.Threading.Thread t = new System.Threading.Thread(threadStart);
//We start the other thread (that will run in parallel) and pass "this" as the parameter.
t.Start(this);
//We loop over and over, sleeping, whilst the other function runs at the same time. This is called "multi- threading"
while (q.Count == 0)
{
System.Threading.Thread.Sleep(10);
}
//This gets the last-entered (oldest) value from the queue q.
string result = q.Deque();
}
}
So this sets off a thread to get the result, and then in my version, polls the queue for a while until the results come back, but in yours could do a bunch of stuff, as long as you check the queue every now and again for new data.
Edit: Added commenting to hopefully alleviate some of your questions.
Could be this approach a valid solution for you? I think you only are using Timer to wait the serialPort to be open, but it can be self-controlled with raising event.
public class SensorReader
{
private Sensor sensor;
private string lastResponse;
public SensorReader(SerialPort serialPort)
{
this.serialPort = aSerialPort.
this.sensor = new Sensor(serialPort);
this.sensor.PortOpen += PortOpenEventHandler(OnPortOpen);
}
private void OnPortOpen()
{
this.ReadPort();
}
public string ReadPort(byte address)
{
if (!this.sensor.IsOpen)
{
this.sensor.OpenPort();
this.lastResponse = "The serial port doesn't respond... yet!";
}
else
{
// Read response at this point.
this.lastResponse = this.GetResponse();
}
return this.lastResponse;
}
}
public class Sensor
{
private SerialPort serialPort;
public Sensor(SerialPort aSerialPort)
{
this.serialPort = aSerialPort;
}
public bool IsOpen
{
get { return this.serialPort.IsOpen; }
}
public delegate void PortOpenEventHandler(object sender, EventArgs e);
public event PortOpenEventHandler PortOpen;
public void OpenPort()
{
// Open port here...
// ... and throw the PortOpen event.
if (this.PortOpen != null)
{
this.PortOpen(this, EventArgs.Empty);
}
}
}
This is what I want to do:
Have a timer with some interval
In the timer callback code, if some condition is met, another thread should be run
I’ve put my code in a class which is instantiated by the main form and the code is executed upon method call (‘StartSync()’, se sample code).
The problem is that the code runs for a couple of seconds but then terminates. I suppose I’m doing something stupid but I really can’t see what it is. Thankful for any help with regards to this.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Diagnostics;
namespace WindowsFormsApplication1
{
class Syncer
{
static bool SYNC_IN_PROGRESS;
public void StartSync()
{
SYNC_IN_PROGRESS = false;
Timer timer = new Timer(timerCallback, null, 0, 1000);
}
public void timerCallback(Object stateInfo)
{
Debug.WriteLine("Sync?");
if (!SYNC_IN_PROGRESS)
{
SYNC_IN_PROGRESS = true;
Thread thSync = new Thread(new ThreadStart(sync));
thSync.Start();
}
}
void sync()
{
Debug.WriteLine("Syncing...");
SYNC_IN_PROGRESS = false;
}
}
}
At a guess, the Timer is only held in a method variable; it sounds to me like the Timer is getting garbage collected and finalized, hence terminated. I suspect you should hold onto that reference in a field to prevent collection.
As an aside - I doubt it is the cause here, but when dealing with threading you should be religiously aware of access to shared state from multiple threads; for example:
using Monitor (aka lock)
appropriate use of volatile
Interlocked when it fits
Your current access to the static bool will probably work OK, but...
Try this cleaner approach
static volatile bool SYNC_IN_PROGRESS;
static thread syncPoll;
public void StartSync()
{
SYNC_IN_PROGRESS = false;
syncPoll = new Thread(sync);
syncPoll.Start();
}
void sync()
{
while (true)
{
Debug.WriteLine("Sync?");
if (SYNC_IN_PROGRESS) Debug.WriteLine("Syncing...");
Thread.Sleep(1000);
}
}
It does the same you try to do with your current code :) but doesn't use a timer
So here is what I did and it seems to work just fine
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
StartSync();
}
static bool SYNC_IN_PROGRESS;
public void StartSync()
{
SYNC_IN_PROGRESS = false;
System.Threading.Timer timer = new System.Threading.Timer(timerCallback, SYNC_IN_PROGRESS, 0, 1000);
}
public void timerCallback(Object stateInfo)
{
Debug.WriteLine("Sync?");
if (!(bool)stateInfo)
{
SYNC_IN_PROGRESS = true;
Thread thSync = new Thread(new ThreadStart(sync));
thSync.Start();
}
}
void sync()
{
Debug.WriteLine("Syncing...");
SYNC_IN_PROGRESS = false;
}
}