Create Form from Async-Callback

G

Guest

Hi all

I stumbled over a new problem:
I have a programm with just a class that is asynchronous listening for
network connections. As soon as someone connected, a new form needs to be
created.
The Form gets created but hangs after creation. I'ts logical that that
happens because the new Form doesn't get a Message Loop.
The Message Loop is created on Application.Run() on the Main Method on the
Main Thread. The Callback is (normally) executed on another Thread so the
Form which is created there doesn't have this Message Loop and therefore
hangs.

Basically I would need to run the creation-code of the new Form on the Main
Thread. But how? I don't have a Form to call Invoke. Well I could make a
dummy form and pass that one to my Callback and invoke it there but that
doesn't seem very clean. I just need to somehow execute some code on the Main
Thread so that the Form is created there and can use the MessageLoop that is
waiting there.

Any Ideas how to solve that in a proper way?

At the end, you'll find a very simple example showing the problem. Form1 is
just needed that I can connect to the Application itself. You could also
comment out the Form1 Stuff and just use "telnet.exe localhost 1234" to
connect to the Application.
After the Connection, the Async-Callback from the class "Test" creates a new
Form with the "Create" Method. And there is where I need help. This "Create"
Method needs to run on the Main Thread so it can use it's Message Loop.

Thanks for help.
//Roman

/************ CODE ************/
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Windows.Forms;

namespace AsyncTest {
public class Form1 : Form {
private Button myButton;

public Form1() {
// Create Button
myButton = new Button();
myButton.Text = "Connect";
myButton.Click += new System.EventHandler(this.button1_Click);

// Add the Button
this.Controls.Add(myButton);
}

private void button1_Click(object sender, EventArgs e) {
// Just Connect...
TcpClient cl = new TcpClient("localhost", 1234);
// ... and disconnect
cl.Client.Close();
cl.Close();
}

protected override void OnClosed(EventArgs e) {
base.OnClosed(e);
Application.Exit();
}

[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);

// Just needed for a Button to tell when to Connect, Could also
use Telnet
Form f = new Form1();
f.Show();

// Start the "listener"
Test t = new Test();

Application.Run();
}
}

class Test {
TcpListener m_tcpListener;

public Test() {
// Start Listener
m_tcpListener = new TcpListener(IPAddress.Any, 1234);
m_tcpListener.Start();
// Start Accepting
m_tcpListener.BeginAcceptTcpClient(new
AsyncCallback(AcceptTcpClientCallback), this);
}

private static void AcceptTcpClientCallback(IAsyncResult ar) {
Test cl = (Test)ar.AsyncState;
TcpListener listener = cl.m_tcpListener;
TcpClient client = listener.EndAcceptTcpClient(ar);

Console.WriteLine("Connected");

// Start Accepting again
listener.BeginAcceptTcpClient(new
AsyncCallback(AcceptTcpClientCallback), cl);

// This Method needs to run on the "main"-Thread where the
MessageLoop is running
// (which was started with Application.Run() )
cl.Create();
}

private void Create() {
Form bla = new Form();
bla.Show();
}
}
}
/************ END CODE ************/
 
W

Wei-Dong XU [MSFT]

Hi,

Thanks for posting at the newsgroup!

"The Message Loop is created on Application.Run() on the Main Method..."
As you have posted, this is the cause why the form failed. So we can find
one resolution to create the form in the main thread. I have one suggestion
for you:

At .Net 2.0 winform. there is one component called BackgroundWorker
provided by Microsoft for you, which can execute one long-time processing
job and the form can communicate with it very smoothly.

It provides the asynchronization to the WinForm. We can create one callback
function for its DoWork delegate which will execute the job at the
background. Then at the callback function, when some special event occur,
we can call the BackgroundWorker.ReportProgress method to report the
status. At the call of the ReportProgress method, BackgroundWorker will
invoke the callback function of its ProgressChanged delegate. Then we can
create one new form and display the information at the callback function
which is safe and the whole development will be quite easy.

The sample from Mark of Windowsforms.net will illustrate for you regarding
how to use this for your asynchronization work.
DataGridView App using BackgroundWorker for Async Data Load
http://www.windowsforms.net/Default.aspx?tabindex=4&tabid=49

Please feel free to let me know if you have any further question on this
issue.

Have a nice day!

Best Regards,
Wei-Dong XU
Microsoft Support
 
G

Guest

Thanks for you answer.

But I think BackgroundWorker won't help me much because I don't have a Form
or a Control that I can refer to.

All I have is a Main-Thread and a Message-Loop running on it. And now, some
other Thread (an Async-Callback in my case) needs to create a Form which
hooks into this Message-Loop on the Main-Thread. That should be possible if I
can somehow marshal the code which creates the new Form into the Main-Thread.
And that's where I hang. I played around with ISynchronizeInvoke and created
my own class that implements it on the Main-Thread with no success.

So I would still be very pleased if someone can give me more infos or maybe
another solution.
 
W

Wei-Dong XU [MSFT]

Hi,

Thanks for the reply ! Currently I think I should write one sample code on
how to instantiate one Form at the main thread. :)

If we need the message loop in the main thread, we will need to call the
Application.Run method and then run the code to execute the underlying
wait. Perhaps I was not very clear when introducing the BackgroundWorker
which, in fact, is not a windows control. It is just one utility class for
us to execute the asynchronous operation. It encapsulates one events list
and provides very easy interface for us to respond to the special events
occuring in the asynchronous thread.

So for your issue, I have one workaround: we can create one form to start
the BackgroundWorker, using it to execute your long-time processing. At
first, we can hide the form and when you are going to display something in
the UI, you can make the form visible with the mesage. For your
convenience, I provide one sample code at the end of this reply.

In addition, if you dislike to inherit your form from Form class, please
feel free to let me know. I will try the best to be of assistance for you.

Have a nice day!

Best Regards,
Wei-Dong XU
Microsoft Support
---------------------------------------------------------------------------
This posting is provided "AS IS" with no warranties, and confers no rights.
---------------------------------------------------------------------------
It is my pleasure to be of any assistance.


//-------------------------------------------------------------------------
// please paste the code in one cs file and name the file. Compile it in
this command line:
//csc /t:winexe <your file name>.cs

using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;

namespace AsynchronousCallBackForm
{
class Program
{
static void Main(string[] args)
{
Application.Run(new MyForm());
}

internal class MyForm : Form
{
private BackgroundWorker bw;
private Label lbl;
private int i;
public MyForm()
{
bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.ProgressChanged += new
ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerCompleted += new
RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
lbl = new Label();
lbl.Text = string.Empty;
Controls.Add(lbl);
this.Load += new EventHandler(MyForm_Load);
}

void MyForm_Load(object sender, EventArgs e)
{
Visible = false;
bw.RunWorkerAsync();
}



void bw_DoWork(object sender, DoWorkEventArgs e)
{
while (i < Int32.MaxValue)
{
if (++i % 100 == 0)
{
BackgroundWorker bw = (BackgroundWorker)sender;
bw.ReportProgress(i);
}
Thread.Sleep(5);
}
}

void bw_ProgressChanged(object sender, ProgressChangedEventArgs
e)
{
if (!Visible)
Visible = true;
Controls[0].Text = e.ProgressPercentage.ToString();
}

void bw_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
Application.Exit();
}
}
}
}
//-------------------------------------------------------------------------
 
G

Guest

Hmm I played around with BackgroundWorker, SynchronizationContext and
ExecutionContext but couldn't achieve what I wanted.

Here's another very simplified example of what I need:

/************ CODE ************/
using System;
using System.Net.Sockets;
using System.Net;
using System.Windows.Forms;

class Program {
static void Main(string[] args) {
StartListening();
Application.Run();
}

static void StartListening() {
TcpListener m_tcpListener = new TcpListener(IPAddress.Any, 1234);
m_tcpListener.Start();

m_tcpListener.BeginAcceptTcpClient(new
AsyncCallback(AcceptTcpClientCallback), m_tcpListener);
}

private static void AcceptTcpClientCallback(IAsyncResult ar) {
TcpListener listener = (TcpListener)ar.AsyncState;
TcpClient client = listener.EndAcceptTcpClient(ar);

listener.BeginAcceptTcpClient(new
AsyncCallback(AcceptTcpClientCallback), listener);

// This Method has to run in the "Main Thread", where
Application.Run() was fired
CreateForm();
}

static void CreateForm() {
Form myForm = new Form();
myForm.Show();
}
}
/************END CODE ************/

Just run it as an Windows Application. You won't see anything. After you
started it, just use the Command Prompt with "telnet localhost 1234"

That will fire the Async Callback which calls CreateForm and creates a new
Form. Now if you try to interact with the Form you'll see that the Form
doesn't respond.

So my problem is, that I don't know how I can marshal CreateForm() to the
Main-Thread, where the Message-Loop is running so that the newly created Form
can interact to Messages.
 
W

Wei-Dong XU [MSFT]

Hi,

I think that will be helpful to instantiate one form with visible to false
at the main thread. This way, the customer will not see the form. Then at
the 2nd thread, it only need to set the visible to true and the form will
be displayed and the user can interact with the form. All this is
demonstrated at my sample code in the last reply. Please have a test.
Thanks!

Besides, from your sample code, the application is waiting for the client
request. Its behavior is very like one server and ASP.net team have one
sample server application with the source code. That application has one UI
for the user interaction. So I'd suggest you can have a look at that
application whose form is created at the main thread. For your scenario,
let's set the visible property of the form to false will resolve your
issue.
Download ASP.NET Cassini Sample Web Server
http://www.asp.net/Projects/Cassini/Download/Default.aspx?tabindex=0&tabid=1

Please feel free to let me know if you have any further question.

Have a nice day!

Best Regards,
Wei-Dong XU
Microsoft Support
 
G

Guest

Thanks, I'll have a look at the Cassini Sample Web Server.

The Problem with your code in the post before was, that, le'ts say, every
Client that connects would open a Form. So that means there could be
unlimited Forms so I need do genereate them when a Client connects.

I by now found a little Workaround which looks like:

/************ CODE ************/
using System;
using System.Net.Sockets;
using System.Net;
using System.Windows.Forms;
using System.Threading;

class Program {
static SynchronizationContext ctx;

static void Main(string[] args) {
Control ctl = new Control();
ctx = SynchronizationContext.Current;
StartListening();
Application.Run();
}

static void StartListening() {
TcpListener m_tcpListener = new TcpListener(IPAddress.Any, 1234);
m_tcpListener.Start();
m_tcpListener.BeginAcceptTcpClient(new
AsyncCallback(AcceptTcpClientCallback), m_tcpListener);
}

private static void AcceptTcpClientCallback(IAsyncResult ar) {
TcpListener listener = (TcpListener)ar.AsyncState;
TcpClient client = listener.EndAcceptTcpClient(ar);
listener.BeginAcceptTcpClient(new
AsyncCallback(AcceptTcpClientCallback), listener);

// This Method has to run in the "Main Thread", where
Application.Run() was fired
Program.ctx.Send(new SendOrPostCallback(CreateForm), null);
}

static void CreateForm(object o) {
Form myForm = new Form();
myForm.Show();
}
}
/************ END CODE ************/

Well, the most important part is that I added an SynchronizationContext
which I set on the Main Thread and then run the CreateForm on that Context
(ctx.Send()). So this solution is working so far but has something that
annoys me:

The first Line in the Main() is a
Control ctl = new Control();
Without this line, the SynchronizationContext.Current would be null so I
can't Send something to it.

I looked around in Reflector and found in the Constructor of a Control the
Line:
WindowsFormsSynchronizationContext.InstallIfNeeded();

Most of what's going on in this Function is Internal so I can't just call it
in my Programm.
So I guess I have to life with that.

So, do you think that this solution with the SynchronizationContext and it's
Send and the dummy Control is ok and solid?

Thanks!
//Roman
 
W

Wei-Dong XU [MSFT]

Hi,

Thanks for the replying!

I have read your code sample, that looks well at the first glance. However,
since the SynchronizationContext class is provided in .Net 2.0 for
"Provides the basic functionality for propagating a synchronization context
in various synchronization models". I think this should not be the best
resolution for your application. This is because the main thread should be
notified to create one form when one client connection is established at
the working thread; at your code, we force one synchronization to make the
main thread creating one form which will hurt your application's
performance that the working thread will need to wait for the main thread
finishes the consturction of one form.

As what you have posted, the SynchronizationContext also introduces some
new problems. So my suggestions is that: it is the best for you to add one
queue at your mainthread to receive the notification from the working
thread. When it receives any notification, it can perform the corresponding
operation. This will make your application more robust and stable. There is
one CodeProject article demonstrating for you regarding how to create
in-process asynchronous services.
Create in-process asynchronous services in C#
http://www.codeproject.com/csharp/inprocessasynservicesincs.asp

Please feel free to let me know if you have any further question on this
issue.

Have a nice day!

Best Regards,
Wei-Dong XU
Microsoft Support
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top