C # WPF - задачи против многопоточности и обновление компонентов пользовательского интерфейса из них - PullRequest
2 голосов
/ 31 мая 2019

Так что в основном я использую C # для написания WPF-приложения для сканирования портов, которое будет сканировать более тысячи комбинаций портов и IP-адресов (x.x.x.x: xx). В результате я должен разделить этот процесс на несколько потоков, чтобы ускорить процесс и избежать зависания пользовательского интерфейса. Ниже показан мой код для MainWindow:

namespace PortScannerTool
{
    public partial class MainWindow : Window
    {

        public MainWindow()
        {
            InitializeComponent();
            tb_portstatus.Text = "Click the button to query ports";
            dns_info.Text = tb_portstatus.Text;
        }


        private void Btn1_Click(object sender, RoutedEventArgs e)
        {

            CmdExec cmdobj = new CmdExec("127.0.0.0","8080");
            Thread query_status = new Thread(cmdobj.Runcmd);
            query_status.Start();
            tb_portstatus.Text = "Done!";
        }
    }
}
namespace PortQueryApp
{
    class CmdExec
    {

        string command;
        public string dnsText;
        public string querystatusText;

        public CmdExec(string ip, string port)
        {
           this.command = $"-n {ip} -o {port}";
        }

        public void Runcmd()
        {
            Process proc = new Process();
            proc.StartInfo.FileName = "PortQry.exe";
            proc.StartInfo.Arguments = this.command;
            proc.StartInfo.CreateNoWindow = true;
            proc.StartInfo.UseShellExecute = false;
            proc.StartInfo.RedirectStandardOutput = true;
            proc.Start();
            string query_output = proc.StandardOutput.ReadToEnd();
            proc.WaitForExit();

            string resolve_pass = "Name resolved";
            string resolve_fail = "Failed to resolve IP address to name";

            if (query_output.Contains(resolve_pass))
            {
                this.dnsText = "Resolved!";
            }
            if (query_output.Contains(resolve_fail))
            {
                this.dnsText = "Resolve Failed!";
            }

            this.querystatusText = "Done!";

        }
    }
}

dns_info и tb_portstatus являются текстовыми полями XAML. CmdExec.Rumcmd - это метод, который выполняет собственно сканирование портов. Если вы посмотрите на класс CmdExec, PortQry.exe - это инструмент для запроса портов Microsoft .

Итак, я хочу, чтобы метод cmdobj.Runcmd обновлял пользовательский интерфейс после его запуска. Но я не знаю, как это сделать, я много гуглил это, и некоторые люди предлагают использовать делегаты, Задачи и BackgroundWorkers, не уверенные, какой метод лучше всего подойдет для моего случая.

Может ли кто-нибудь дать какие-нибудь предложения или указать мне какие-либо руководства? Это было бы здорово! Спасибо!

Редактировать: Большинство примеров имеют многопоточный метод в том же классе. Я не уверен, требуется ли это, но я должен использовать:

 class CmdExec : MainWindow
{
   //Do stuff
}

вместо

Ответы [ 3 ]

2 голосов
/ 31 мая 2019

Что вы ДОЛЖНЫ сделать: Дайте некоторым событиям CmdExec, чтобы они могли быть запущены, когда что-то меняется, например:

public delegate void CombinationFoundDelegate(object sender, int port, string ip); 
//note that it would be better to create class derived from EventArgs instead of passing bare arguments in this delegate
public class CmdExec
{
    public event CombinationFoundDelegate CombinationFound;

    public void Runcmd()
    {
       //do your work and call event when necessary:
       if(CanConnect(ip, port))
         CombinationFound?.Invoke(this, port, ip);
    }
}

А потом в вашей форме просто послушайте это событие:

cmdobj.CombinationFound += new CombinationFoundCallback;

И все обновления GUI могут происходить при этом обратном вызове. Но вы должны знать, что этот обратный вызов вызывается из другого потока (поскольку CmdExec работает в другом потоке), поэтому вам нужно синхронизировать элементы управления. Вы можете использовать его Dispatcher для этого случая:

Action a = new Action(() => textBox.Text = ip); 

if(!Dispatcher.CheckAccess())
   Dispatcher.Invoke(a);
else
   a();

Итак, обратите внимание, что CheckAccess по какой-то причине скрыт в Intellisense, но он действительно там. CheckAccess просто проверяет, требуется ли синхронизация или нет (в Winforms это будет InvokeRequired).

Далее, если требуется синхронизация, вы можете запустить свой код обновления пользовательского интерфейса в методе вызова диспетчера. Однако, если не требуется синхронизация, вы можете просто запустить свой код пользовательского интерфейса здесь. Вот почему я создал Action для этого, чтобы не дублировать код.

И это все. (Код написан из головы, поэтому с реальным кодом может быть какая-то разница, но так он должен выглядеть и работать).

0 голосов
/ 31 мая 2019

Вы должны использовать асинхронность без потоков (по крайней мере, без управляемых потоков).

Вы можете ждать работу, проделанную в процессе, с пользовательским ожидающим. Работа для этого довольно сложная. К счастью, кто-то уже сделал это.

PM> Install-Package RunProcessAsTask

Тогда ваш код просто становится

    public async Task<bool> Runcmd()
    {
        ProcessResults results = await ProcessEx.RunAsync(
            "PortQry.exe",
             command
        );
        if(results.StandardOutput.Any(output => output.Contains(resolve_pass)))
           return true;
        return false;
    }
0 голосов
/ 31 мая 2019

Я думаю, что BackgroundWorker - лучший выбор. В WPF вам не разрешено вносить изменения в пользовательский интерфейс из другого потока. Таким образом, простое перемещение кода в методе обработчика событий вызывает проблему при отражении результата процесса в пользовательском интерфейсе (например, отображение сообщения «выполнено»).

Это то же самое в BackgroundWorker.DoWork, но BackgroundWorker's ReportProgress и RunWorkerCompleted могут помочь вам сделать ваш код простым. Посмотрите ответ здесь . Я думаю, что вы можете легко понять это.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...