Synchronize an async Task in WinForms-Validating-Event - c#

I have a classic WinForms app which communicates async with the server api. That is realized by async await pattern.
Now I have a problem with the 'Validating'-Event. We use this event for client-side Validation (check if input is null) and set cancel.
If input is valid then I send the input to server async and await the result.
There is my problem. Now the control means that it is valid. When the callback set the cancel event to false it is too late.
Here ist my code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public string CustomerName { get; set; }
private async void Button1_Validating(object sender, CancelEventArgs e)
{
if (CustomerName == null)
{
e.Cancel = true;
}
else
{
var isValidAndSaved = await SaveOnServerAsync(CustomerName);
// Here is the Problem: Setting e.Cancel in callback is too late.
if (isValidAndSaved)
{
e.Cancel = false;
}
else
{
e.Cancel = true;
}
}
}
/// <returns>True if the given model was valid and save operation was successfull.</returns>
public async Task<bool> SaveOnServerAsync(string customerName)
{
await Task.Delay(3000); // Send given customerName to server in real app.
return customerName.Contains("%") ? false : true;
}
}
I tried classic programming (synchron) for this case by 'Wait' the taks in main thread. But then I generated a deadlock.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public string CustomerName { get; set; } = "3";
private void Button1_Validating(object sender, CancelEventArgs e)
{
if (CustomerName == null)
{
e.Cancel = true;
}
else
{
// Deadlock because MainThread waits for callback.
var isValidAndSaved = SaveOnServerAsync(CustomerName).Result;
if (isValidAndSaved)
{
e.Cancel = false;
}
else
{
e.Cancel = true;
}
}
}
/// <returns>True if the given model was valid and save operation was successfull.</returns>
public async Task<bool> SaveOnServerAsync(string customerName)
{
await Task.Delay(3000); // Send given customerName to server in real app.
return customerName.Contains("%") ? false : true;
}
}
Has anyone an idea for a good pattern?
If I set Cancel to false before await the async task then my problem would be that the user click is discarded and the user experience is sufficient.

I found a working way. Except the first Task all other tasks need "ConfigureAwait(false)".
See method 'SaveOnServerAsync'.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public string CustomerName { get; set; } = "3";
private void Button1_Validating(object sender, CancelEventArgs e)
{
if (CustomerName == null)
{
e.Cancel = true;
}
else
{
var isValidAndSaved = SaveOnServerAsync(CustomerName).Result;
if (isValidAndSaved)
{
e.Cancel = false;
}
else
{
e.Cancel = true;
}
}
}
/// <returns>True if the given model was valid and save operation was successfull.</returns>
public async Task<bool> SaveOnServerAsync(string customerName)
{
await Task.Delay(3000)
// The solution is that all deeper Tasks need line below.
.ConfigureAwait(false);
;
return customerName.Contains("%") ? false : true;
}
}
I found many articles about that problem. It sounds a bit of missconcept from MS.
Figure 3 of this MS article explains the deadlock situation:
https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming
The solution to avoid deadlocks is explained here:
https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming#configure-context

Related

Send the progress bar as a parameter to a function in another class that return Task<bool> Type

I want to send a progress bar that is available on my form to a function to display the progress of the operation of that function and finally return true if everything is fine and return false if else
The operation inside the SaveBadCustomerMustStayMasdoodExcelFile function is executed asynchronously so as not to interfere with the execution of other operations.
When the program runs and the compiler calls the line _ProgressBar.Invoke((MethodInvoker)(() => {_ProgressBar.Value = Convert.ToInt32(i);})); Inside the badCustomers.SendToDB(progressBar_BadCustomers) function, there is no feedback and the program seems to be stuck in an infinite loop.
But if the output of the SaveBadCustomerMustStayMasdoodExcelFile function is defined as void, everything works fine.
My code when the system hangs:
In Button:
private void btn_ChoiceBadCustomersFile_Click(object sender, EventArgs e)
{
try
{
DialogResult dialogResult = MessageBox.Show("message", "title",
MessageBoxButtons.YesNo);
if ((dialogResult == DialogResult.Yes))
{
bool result = false;
try
{
result = GetDataFromExcelFile.SaveBadCustomerMustStayMasdoodExcelFile(
progressBar_BadCustomers).Result;
}
catch
{
result = false;
}
if (result)
{
//code...
}
}
}
catch
{
//code...
}
}
Code In GetDataFromExcelFile.SaveBadCustomerMustStayMasdoodExcelFile(...)
public static class GetDataFromExcelFile
{
public async static Task<bool> SaveBadCustomerMustStayMasdoodExcelFile(
DevComponents.DotNetBar.Controls.ProgressBarX progressBar_BadCustomers)
{
try
{
PoomaDbAppEntities DB10 = new PoomaDbAppEntities();
IQueryable<tbl_BadCustomers> dt = null;
MyExcelWorkSpace _excelApp = new MyExcelWorkSpace();
MyExcelWorkSpace.badCustomers badCustomers = new MyExcelWorkSpace
.badCustomers();
string path = badCustomers.select();
if (path != String.Empty)
{
if (badCustomers.Open(path))
{
try
{
await Task.Run(() => { dt = badCustomers
.SendToDB(progressBar_BadCustomers); });
return true;
}
catch
{
return false;
}
}
else
{
return false;
}
}
else
{
return false;
}
}
catch
{
return false;
}
}
}
And In badCustomers.SendToDB(...) :
public class badCustomers : ExcelFile, IStartWorkWithExcelFile<tbl_BadCustomers>
{
//code
public IQueryable<tbl_BadCustomers> SendToDB(DevComponents.DotNetBar
.Controls.ProgressBarX _ProgressBar)
{
try
{
//code
_ProgressBar.Invoke((MethodInvoker)(() => {
_ProgressBar.Value = Convert.ToInt32(i); }));
}
catch
{
//code
}
}
IQueryable<tbl_BadCustomers> dt = DB10.tbl_BadCustomers.Select(i => i);
return dt;
}
}
If the SaveBadCustomerMustStayMasdoodExcelFile function is defined as below, the program works, but I need to know if an error has occurred or not.
public async static void SaveBadCustomerMustStayMasdoodExcelFile(
DevComponents.DotNetBar.Controls.ProgressBarX progressBar_BadCustomers)
{
//code
}
If the SaveBadCustomerMustStayMasdoodExcelFile function is defined as below, the program works, but I need to know if an error has occurred or not.
public async static void SaveBadCustomerMustStayMasdoodExcelFile(
DevComponents.DotNetBar.Controls.ProgressBarX progressBar_BadCustomers)
{
//code
}
there is no feedback and the program seems to be stuck in an infinite loop.
This is because the code is using Result; full details on my blog.
To solve this, you want to avoid async void (i.e., for SaveBadCustomerMustStayMasdoodExcelFile); async void is intended for event handlers, so you can make btn_ChoiceBadCustomersFile_Click an async void method. Everything else should use await and async Task.
As far as the Progress goes, I echo the recommendations in the comments: IProgress<T> with Progress<T> makes your code cleaner (e.g., no Invoke necessary).

Async-await in multiple Winforms projects

We're trying to get status updates in the UI while the threads are running in the background. The following code is supposed to allow it but in practice we get the updates only once all threads are done and not while they are running. We also don't see significant performance improvement compared to running the task in serial so we might be doing something wrong here
The solution includes two projects with winForm with the first calling the second. WinClient namespace is used for the Winform client. It calls Services.modMain:
namespace WinClient
{
static class Program
{
[STAThread]
static void Main()
{
//call another winform project and wait for it to complete
Services.modMain.loadObjects().Wait();
//run local form
Application.Run(new Form1());
}
}
}
Service.modMain is where the application is continuously getting data and updating it in memory. When it does, it writes status messages to a splash form which remains open all the time. Once Service.modMain finishes the initial data load, Form1 (empty form in this exampl) should open while splashForm remains open as well
namespace Services
{
public static class modMain
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
public static void Main()
{
}
public static async Task loadObjects()
{
frmSplash.DefInstance.LoadMe();
Progress<PrintToSplashMessage> messageToWindow = new Progress<PrintToSplashMessage>();
messageToWindow.ProgressChanged += reportProgress;
frmSplash.DefInstance.print_to_window("Starting Services", Color.Black, true);
Task<bool> load1Task = load1(messageToWindow);
Task<bool> load2Task = load2(messageToWindow);
await Task.WhenAll(load1Task, load2Task);
}
private static async Task<bool> load2(IProgress<PrintToSplashMessage> progress)
{
return await Task<bool>.Run(() =>
{
PrintToSplashMessage theMessage = new PrintToSplashMessage("Load2, please wait...", Color.Black, true, false);
progress.Report(theMessage);
for (int i = 0; i != 100; ++i)
{
Thread.Sleep(100); // CPU-bound work
}
return true;
});
}
private static async Task<bool> load1(IProgress<PrintToSplashMessage> progress)
{
return await Task<bool>.Run(() =>
{
PrintToSplashMessage theMessage = new PrintToSplashMessage("Load1, please wait...", Color.Black, true, false);
progress.Report(theMessage);
for (int i = 0; i != 100; ++i)
{
Thread.Sleep(100); // CPU-bound work
}
return true;
});
}
private static void reportProgress(object sender, PrintToSplashMessage e)
{
frmSplash.DefInstance.PrintToSplashWindow(e);
}
}
}
PrintToSplashWindow is just a utility class to store progress data:
namespace Services
{
public class PrintToSplashMessage
{
public string Message { get; set; }
public Color MessageColor { get; set; }
public bool OnNewLine { get; set; }
public bool PrintToLog { get; set; }
public PrintToSplashMessage(String theMessage, Color theMessageColor, bool isOnNewLine, bool needPrintToLog)
{
Message = theMessage;
MessageColor = theMessageColor;
OnNewLine = isOnNewLine;
PrintToLog = needPrintToLog;
}
}
}
Finally, here's frmSplash:
namespace Services
{
public partial class frmSplash : Form
{
public frmSplash() :base()
{
InitializeComponent();
}
public void PrintToSplashWindow(PrintToSplashMessage theMessage)
{
print_to_window(theMessage.Message, theMessage.MessageColor, theMessage.OnNewLine);
}
public void print_to_window(string strShortMsg, Color lngColor, bool blnOnNewLine)
{
string strNewLine = String.Empty;
if (blnOnNewLine)
{
if ( rtbErrorDisplay.Text.Length > 0)
{
strNewLine = Environment.NewLine;
}
else
{
strNewLine = "";
}
}
else
{
strNewLine = "";
}
rtbErrorDisplay.SelectionStart = rtbErrorDisplay.Text.Length;
rtbErrorDisplay.SelectionColor = lngColor;
rtbErrorDisplay.SelectedText = strNewLine + strShortMsg;
rtbErrorDisplay.SelectionStart = rtbErrorDisplay.Text.Length;
rtbErrorDisplay.ScrollToCaret();
Application.DoEvents();
}
}
}
What we expect is that frmSplash would show the progress messages as the tasks are runing in the background. In practice, it only show all at bulk when everything is done.
Short version: the only thing that ever processes window messages in the code you posted is a call to Application.DoEvents(). But the code likely never gets that far, or if it does, the call happens on the wrong thread.
Longer version:
You didn't include an actual MCVE, so I didn't bother to test, but the Progress class relies on a synchronization context to work. Since you haven't called Application.Run(), there's may be no sync context at all. In which case Progress is just going to use the thread pool to invoke whatever handlers subscribed to it.
That would mean that when you call Application.DoEvents(), you're in a thread pool thread, not the thread that owns your splash window.
Windows are owned by threads, and their messages go to that thread's message queue. The Application.DoEvents() method will retrieve messages for the current thread's message queue, but does nothing to process messages for other threads' queues.
In the worst case, there is a sync context for that thread (I can't recall…it's possible that since the thread is STA, the framework has created one for you), but since you have no message loop, nothing queued to it ever gets dispatched. The progress reports just keep piling up and never processed.
You should abandon Application.DoEvents() altogether. Calling DoEvents() is always a kludge, and there's always a better option.
In this case, use Application.Run() for the first form as well (the splash screen). Create that form and subscribe to its FormShown event so that you know when to call loadObjects(). At the end of that method, close the form, so Application.Run() will return and go on to the next Application.Run() call.
Here is a sample based on the code you did post, with me filling in the details (for both forms, just use the Designer to create a default Form object…the rest of the initialization is in the user code below).
For the splash screen class, I inferred most of it, and took the rest straight from your code. The only change I made to your code was to remove the call to Application.DoEvents():
partial class SplashScreen : Form
{
public static SplashScreen Instance { get; } = new SplashScreen();
private readonly RichTextBox richTextBox1 = new RichTextBox();
public SplashScreen()
{
InitializeComponent();
//
// richTextBox1
//
richTextBox1.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
richTextBox1.Location = new Point(13, 13);
richTextBox1.Name = "richTextBox1";
richTextBox1.Size = new Size(775, 425);
richTextBox1.TabIndex = 0;
richTextBox1.Text = "";
Controls.Add(richTextBox1);
}
public void PrintToSplashWindow(PrintToSplashMessage theMessage)
{
print_to_window(theMessage.Message, theMessage.MessageColor, theMessage.OnNewLine);
}
public void print_to_window(string strShortMsg, Color lngColor, bool blnOnNewLine)
{
string strNewLine = String.Empty;
if (blnOnNewLine)
{
if (richTextBox1.Text.Length > 0)
{
strNewLine = Environment.NewLine;
}
else
{
strNewLine = "";
}
}
else
{
strNewLine = "";
}
richTextBox1.SelectionStart = richTextBox1.Text.Length;
richTextBox1.SelectionColor = lngColor;
richTextBox1.SelectedText = strNewLine + strShortMsg;
richTextBox1.SelectionStart = richTextBox1.Text.Length;
richTextBox1.ScrollToCaret();
}
}
It's not clear to me why you have two different classes, both of which seem to be set up as the entry point for the program. I consolidated those into a single class:
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
loadObjects();
Application.Run(new Form1());
}
public static void loadObjects()
{
SplashScreen.Instance.Shown += async (sender, e) =>
{
Progress<PrintToSplashMessage> messageToWindow = new Progress<PrintToSplashMessage>();
messageToWindow.ProgressChanged += reportProgress;
SplashScreen.Instance.print_to_window("Starting Services", Color.Black, true);
Task<bool> load1Task = load1(messageToWindow);
Task<bool> load2Task = load2(messageToWindow);
await Task.WhenAll(load1Task, load2Task);
SplashScreen.Instance.Close();
};
SplashScreen.Instance.ShowDialog();
}
private static async Task<bool> load2(IProgress<PrintToSplashMessage> progress)
{
return await Task.Run(() =>
{
PrintToSplashMessage theMessage = new PrintToSplashMessage("Load2, please wait...", Color.Black, true, false);
progress.Report(theMessage);
for (int i = 0; i < 10; ++i)
{
Thread.Sleep(TimeSpan.FromSeconds(1)); // CPU-bound work
theMessage.Message = $"Load2, i = {i}";
progress.Report(theMessage);
}
return true;
});
}
private static async Task<bool> load1(IProgress<PrintToSplashMessage> progress)
{
return await Task.Run(() =>
{
PrintToSplashMessage theMessage = new PrintToSplashMessage("Load1, please wait...", Color.Black, true, false);
progress.Report(theMessage);
for (int i = 0; i < 10; ++i)
{
Thread.Sleep(TimeSpan.FromSeconds(1)); // CPU-bound work
theMessage.Message = $"Load1, i = {i}";
progress.Report(theMessage);
}
return true;
});
}
private static void reportProgress(object sender, PrintToSplashMessage e)
{
SplashScreen.Instance.PrintToSplashWindow(e);
}
}

DeadLock on task.Wait() with Task which edit UI

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.

C# Winforms - Cannot Access Disposed Object

C# .net 3.5 Winforms application.
So here is the situation. I have a form that runs some code to that opens another forms in another thread. When that form opens, it performs certain tasks and then needs to close if those conditions are met. Problem is, it keeps throwing this exception and I have NO idea why.
]1)
Here are my snippets of code used in all this.:
Here is the function that opens the form in another thread: Program.cs
public static void new_downloader_thread()
{
Thread t = new Thread(new ThreadStart(open_downloader));
t.SetApartmentState(ApartmentState.STA);
if (Open_Downloader)
{
t.Start();
}
}
public static void open_downloader()
{
try
{
Application.Run(new DownloadInstall());
}
catch (Exception e)
{
MessageBox.Show(Convert.ToString(e));
}
}
Here is the code from the form that opens the form giving me the trouble: Manager.cs
private void new_System_Click(object sender, EventArgs e)
{
if (Program.Open_Downloader == false)
{
Program.Current_Download = "newsys.exe";
Program.Open_Downloader = true;
Program.new_downloader_thread();
}
else
{
download_busy();
}
}
Here is the form that is opening: DownloadInstall.cs
public partial class DownloadInstall : Form
{
/// Opening Declarations
private static System.Net.WebClient web_client;
private static System.Diagnostics.Stopwatch stop_watch;
private static bool downloading { get; set; }
private static bool is_name_update { get; set; }
private static int download_state { get; set; }
private static int elapsed { get; set; }
private static int opening { get; set; }
//private static int counter;
/// --------------------
public DownloadInstall()
{
InitializeComponent();
is_name_update = false;
downloading = false;
opening = 0;
web_client = new System.Net.WebClient();
web_client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(Download_Progress);
web_client.DownloadFileCompleted += new AsyncCompletedEventHandler(Download_Complete);
stop_watch = new System.Diagnostics.Stopwatch();
/// Keypress event call and blanking out the box text.
this.name_update_field.Text = "";
this.name_update_field.KeyPress += new KeyPressEventHandler(no_period);
/// --------------------------------------------------
/// Changes the text to what file we are downloading.
this.file_name.Text = string.Format("Downloading {0}", Program.Current_Download);
/// -------------------------------------------------
/// Sets a forms closed event handler
this.FormClosing += new FormClosingEventHandler(Download_Window_Closing);
/// ---------------------------------
/// Creates folder where files are to be downloaded and deletes any residual files.
try
{
System.IO.Directory.CreateDirectory(#"C:\Downloads");
}
catch
{
}
if (System.IO.File.Exists(string.Format(#"C:\Downloads\{0}", Program.Current_Download)))
{
try
{
System.IO.File.Delete(string.Format(#"C:\Downloads\{0}", Program.Current_Download));
}
catch
{
if (Program.Disconnecting == false)
{
opening = 1;
}
}
}
/// -------------------------------------------------------------------------------
switch (opening)
{
/// Case 0 Starts the download or name update code normally.
case 0:
if (Program.Current_Download == "Name Update")
{
file_name.Text = "Name Update Downloader";
is_name_update = true;
this.restart_or_start.Text = "Start";
this.cid.Visible = true;
this.name_update_field.ReadOnly = false;
this.name_update_field.Visible = true;
}
else
{
Download_Begin();
}
break;
/// Case 1 will close the downloader.
case 1:
MessageBox.Show("It is possible this file was already downloaded and is already open on this computer.", "Hmmmmm...", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
this.Close();
break;
}
}
/// Function that is called when the window is closed.
private void Download_Window_Closing(object sender, FormClosingEventArgs e)
{
if (downloading)
{
web_client.CancelAsync();
}
Program.Current_Download = "";
Program.Open_Downloader = false;
}
/// --------------------------------------------------
Not sure why this is throwing the exception. It seems if I remove code that closes the form, it works, and I can close the form properly from other parts of the code. Any help would be great!
Solved!!! The problem is when trying to close the form before it has finished construction.
Changed:
this.Close();
to:
Load += (s, e) => Close();

Windows Phone 8.1 TCP sockets

I am developing a Windows Phone 8.1 chat app and discovered multiple difficulties during programming a socket interoperability.
The main issue are 'events' on a socket, and main question is: How to subscribe to OnData event, e.g. how to detect the event, when data came from the server?
I tried to solve it by direct way: infinite reading from InputStream, and that works with 50/50 luck: sometimes that method freeze the UI stream (even the reading method is async).
I googled for that issue, because it's a very strange behavior for that important component, and didn't found any solutions for this even on MSDN. At SO I found only same solutions like mine, but they also freeze UI flow.
For reference, my app infrastructure is the main app that uses facade class from shared library, the facade class get a generic, that specify concrete behavior (this was done for reason of multiple data transports). For TCP transport, I wrote that:
public class TCPTransport : ITransportProvider
{
private bool _connected { get; set; }
private StreamSocket _socket { get; set; }
private DataReader _input { get; set; }
public TCPTransport()
{
_socket = new StreamSocket();
}
public async Task<bool> Connect(string host, int port)
{
try
{
await _socket.ConnectAsync(new HostName(host), port.ToString());
if (OnConnect != null)
OnConnect();
_connected = true;
_input = new DataReader(_socket.InputStream)
{
InputStreamOptions = InputStreamOptions.Partial
};
_read();
return _connected;
}
catch(Exception e)
{
Debug.WriteLine("[warn] cant conect to " + host + ":" + port + ". Additional: " + e.Message);
return false;
}
}
async private void _read()
{
while (true)
{
if (!_connected || _socket == null) break;
uint buffer = await _input.LoadAsync(2048);
string data = _input.ReadString(buffer);
if (OnData != null && data.Length != 0)
OnData(data);
}
}
public async Task<bool> Send(string data)
{
if (!_connected) return false;
try
{
await _socket.OutputStream.WriteAsync(Encoding.UTF8.GetBytes(data).AsBuffer());
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine("[warn] Exception send: " + e.Message);
}
return true;
}
public bool Close()
{
if (!_connected) return false;
_socket.Dispose();
return true;
}
public Action OnConnect { get; set; }
public Action<string> OnData { get; set; }
public Action OnClose { get; set; }
}
I suppose that I am wrong about using that solution, but how can I listen to data from server?
For better or worse, the StreamSocket API is not designed as an event-driven API; it is designed as an asynchronous API. If you want to raise an event when data is available, use an event, not a delegate:
Here is a trivial example (hard-coded to www.microsoft.com and with no error handling) showing how to simulate the event:
Note that if you run with the debugger attached, it will temporarily freeze the UI thread. This doesn't occur when not debugging
// Create your own class to hold the data
public class DataReceivedEventArgs : EventArgs
{
public uint DataCount { get; private set; }
public DataReceivedEventArgs(uint dataCount)
{
DataCount = dataCount;
}
}
// Now inside your MainPage...
bool m_keepReading = false;
bool m_eventHandled = false;
public event TypedEventHandler<MainPage, DataReceivedEventArgs> DataReceived;
// Hook this up to (eg) a Button
private async void StartSockets(object sender, RoutedEventArgs e)
{
if (!m_eventHandled)
DataReceived += (s, args) => Debug.WriteLine(args.DataCount);
m_eventHandled = true;
m_keepReading = true;
DoSocketTestAsync();
}
// Hook this up to (eg) a Button
private void StopSockets(object sender, RoutedEventArgs e)
{
m_keepReading = false;
}
private async Task DoSocketTestAsync()
{
var socket = new StreamSocket();
await socket.ConnectAsync(new HostName("www.microsoft.com"), "http");
var writer = new DataWriter(socket.OutputStream);
writer.WriteString("GET /en-us HTTP/1.1\r\nHOST: www.microsoft.com\r\n\r\n");
await writer.StoreAsync();
ReadSocketAsync(socket);
}
private async void ReadSocketAsync(StreamSocket socket)
{
while (m_keepReading)
{
const uint size = 2048;
IBuffer buffer = new Windows.Storage.Streams.Buffer(size);
buffer = await socket.InputStream.ReadAsync(buffer, size,
InputStreamOptions.Partial);
var handler = DataReceived;
if (handler != null && buffer.Length > 0)
handler(this, new DataReceivedEventArgs(buffer.Length));
}
}
Now when you click the "start" button, assuming you have a network connection you will see some numbers start appearing in the Debug Output window.
(Also note that using private auto-implemented properties doesn't really buy you anything; just make them fields).

Categories

Resources