Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Asynchronous code that works in Console but not in Windows Forms

I am trying to write an application the is constantly searching for host on a lan. When I run this as a console as the countdown.Wait() seems to work fine. However when I bring the code into a windows form the countdown.Signal() does not seem to decrement its counter. Not sure what the problem is.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net.NetworkInformation;
using System.Diagnostics;
using System.Net;
using System.Threading;

namespace Multi_Threaded
{
    public partial class Form1 : Form
    {

    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        PortScanner ps = new PortScanner();
        ps.ProbeCompleted += new PingProbeCompleted(ps_ProbeCompleted);

        ps.run_ping_probe();
    }

    void ps_ProbeCompleted(object sender, PingProbeCompletedArguments e)
    {
        MessageBox.Show("I found " + e.ip_adresses_list_of_host.Count.ToString() + "host(s)");
    }
}

public delegate void PingProbeCompleted(object sender,PingProbeCompletedArguments e);
public class PingProbeCompletedArguments : EventArgs
{
    public List<string> ip_adresses_list_of_host;
}
public class PortScanner
{
    public event PingProbeCompleted ProbeCompleted;
    static List<string> ip_adresses = new List<string>();

    static CountdownEvent countdown;

    public void run_ping_probe()
    {
        ip_adresses.Clear();

        countdown = new CountdownEvent(1);

        string ipBase = "10.125.";
        for (int sub = 0; sub < 14; sub++)
        {
            for (int i = 1; i < 255; i++)
            {
                string ip = ipBase + sub.ToString() + "." + i.ToString();
                Ping p = new Ping();
                p.PingCompleted += new PingCompletedEventHandler(p_PingCompleted);
                countdown.AddCount();
                p.SendAsync(ip, 100, ip);
            }
        }
        countdown.Signal();
        countdown.Wait();
        PingProbeCompletedArguments e = new PingProbeCompletedArguments();
        e.ip_adresses_list_of_host = ip_adresses;
        ProbeCompleted(this, e);

    }

    private void p_PingCompleted(object sender, PingCompletedEventArgs e)
    {
        string ip = (string)e.UserState;
        if (e.Reply.Status == IPStatus.Success)
        {
            ip_adresses.Add(ip + "\t" + e.Reply.RoundtripTime + " ms");
        }
        countdown.Signal();
    }
}
like image 764
jeremyforan Avatar asked Oct 14 '11 11:10

jeremyforan


1 Answers

Yes, your code deadlocks when you use it in a Winforms project. The problem is that the Ping class makes a best effort to raise the PingCompleted event on the same thread that called SendAsync(). It uses the AsyncOperationManager.CreateOperation() method to do so.

Problem is, that actually works in a Winforms app. It tries to raise the event on the main thread. But that cannot work since you blocked the main thread with the countdown.Wait() call. The ping cannot complete since the main thread is blocked. The main thread cannot complete since the ping doesn't complete. Deadlock city.

It works in a Console mode app since it doesn't have a synchronization provider like Winforms does. The PingComplete event will be raised on a threadpool thread.

Blocking the UI thread is fundamentally flawed. The quick fix is to run the code on a worker thread. Beware that this makes the ProbeCompleted event fired on that worker as well. Use Control.BeginInvoke() to marshal it to the UI thread. Or use BackgroundWorker.

    private void Form1_Load(object sender, EventArgs e) {
        PortScanner ps = new PortScanner();
        ps.ProbeCompleted += new PingProbeCompleted(ps_ProbeCompleted);
        ThreadPool.QueueUserWorkItem((w) => ps.run_ping_probe());
    }

And don't forget to remove the extra Signal() call.

like image 59
Hans Passant Avatar answered Oct 12 '22 22:10

Hans Passant