UI нітка блакуе маё акно, як змяніць гэта?

Вось мой код:

public void BroadcastTheConnection()
      {
        try
        {
            //............avoid unnecessary codes 
            while (ServerRunning)
            {
                s = myList.AcceptSocket();// blocking the content until client request accomplished 



                displayText.AppendText("\nConnected");
                Thread tcpHandlerThread = new Thread(tcpHandler);
                tcpHandlerThread.Name = "tcpHandler";
                tcpHandlerThread.Start();

             }

         }catch (Exception ex)
            {
                displayText.AppendText("Error----" + Environment.NewLine + ex.StackTrace);
            }
        }

Гэты код выдатна працуе, калі я спрабую злучыць некалькі кліентаў. Калі я спрабую перанесьці сваю форму пасля таго, як я вяшчаць злучэнне не працуе. Я ведаю, што гэта праблема шматструменнасці, але як я магу пазбегнуць гэтай праблемы?

А вось мая кнопка:

private void bBroadcast_Click(object sender, EventArgs e)
        {
            BroadcastTheConnection();          
        }

Ці павінен я выкарыстоўваць аператары блакавання? або дэлегаты? любыя ідэі? то як?

1
Вы павінны думаць аб стварэнні прысвечанага Тэма толькі для мэт сеткі сувязі. Затым, калі вы хочаце перанесці дадзеныя з сувязі ніткі ў струмень карыстацкага інтэрфейсу, вы павінны выкарыстоўваць некаторы кантэкст сінхранізацыі.
дададзена аўтар m.rogalski, крыніца
Вы павінны змясціць broadcasttheconnection ў нітку
дададзена аўтар BugFinder, крыніца
Такім чынам, яе хатняе заданне ?? Im упэўнены, што вы павінны быць адзначаны на ваша пытанне, як такія, але і ён будзе пакрыў гэтыя паняцці з вамі
дададзена аўтар BugFinder, крыніца
Вы можаце выкарыстоўваць BackgroundWorker для вырашэння лёгкім шляху
дададзена аўтар Mustafa Erdem Köşk, крыніца
@BugFinder - калі вы не пярэчыце, маглі б вы даць мне прыклад ...
дададзена аўтар Hamun Sunga, крыніца

6 адказы

Праблема заключаецца ў тым, што BroadcastTheConnection() выклікаецца з самага UI патоку. Так як ён мае While (ServerRunning) {} пабудаваць, паток UI будзе круціцца на ваш код да ServerRunning ілжывая.

Ёсць некалькі спосабаў рэалізацыі тое ж самае цяжкае становішча :. <�Моцны> атрымаць код сервера ад ніткі UI Кожны з іх мае свае кампрамісы.

  • Выкарыстоўвайце BroadcastTheConnection() як доўга працуе задача (не рэкамендуецца)
  • <�Літый> Устань нітка дзе BroadcastTheConnection() з'яўляецца асноўным метадам.
  • Выкарыстоўваць асінхронны сокет выклікі. (Занадта складаны для хуткага адказу)

<�Моцны> Доўгія Запуск задачы

Task.Factory.StartNew(BroadcastTheConnection,
                      TaskCreationOptions.LongRunning);

Гэта хутка і лёгка, але вы не хочаце занадта шмат працяглых задач, як яны могуць займаць тэмы ў задачы ThreadPool на працягу доўгага часу.

<�Моцны> Dedicated Thread

Thread connectionThread = new Thread(BroadcastTheConnection)
{
    Name = "BroadcaseTheConnection Thread",
    IsBackground = true
};
connectionThread.Start();

Гэта не выкарыстоўвае ніякіх патокаў з задач ThreadPool, дае найменны паток, які можа дапамагчы пры адладцы і прадухіляе нітка ад захоўвання прыкладання працуе, калі вы забыліся скончыць яго.

<�Моцны> Праца з UI з кода сокета

Кожны раз, калі вы павінны ўзаемадзейнічаць з UI ў любым выпадку, вам трэба паставіць выклік ў патоку карыстацкага інтэрфейсу зноў. WinForms і wpf маюць крыху розныя спосабы зрабіць тое ж самае.

WinForms

myControl.BeginInvoke(myControl.Method);//nonblocking

myControl.Invoke(myControl.Method);//blocking

wpf

myControl.Dispatcher.BeginInvoke(myControl.Method);//nonblocking

myControl.Dispatcher.Invoke(myControl.Method);//blocking

Будзьце асцярожныя, занадта шмат выклікаў BeginInvoke у радку можа перагрузіць паток карыстацкага інтэрфейсу. Гэта лепш, чым іх партыі, каб выпаліць шмат запытаў запар.

1
дададзена
Выкарыстоўваючы тэму ў часы TPL абстракцый, не выкарыстоўваецца асінхронным-Чаканне для фонавых аперацый, але з выкарыстаннем APM мадэлі Begin/End Invoke вяртаецца ў гісторыі. Асінхронны-Await з'яўляецца самым простым рашэннем для гэтага выпадку.
дададзена аўтар Mrinal Kamboj, крыніца
<�Код> Invoke блакуе, пакуль паток UI не выконвае код, так што ў рэчаіснасці механізм блакавання.
дададзена аўтар Berin Loritsch, крыніца
<�Код> Invoke() і BeginInvoke() метады на WinForms вызначаецца ў Упраўленне клас. Такім чынам, любое акно, кнопкі, панэлі і г.д., будуць мець функцыю з дазваляе выклікаць код у патоку карыстацкага інтэрфейсу. Гэта ж гісторыя для кода WPF, за выключэннем таго, што яны маюць Дыспетчарская , які дазваляе рабіць тое ж самае.
дададзена аўтар Berin Loritsch, крыніца
Правільна. Калі ласка.
дададзена аўтар Berin Loritsch, крыніца
Глядзіце апошні профіль: Вы павінны Invoke ў патоку карыстацкага інтэрфейсу ад фонавага патоку. Я прывёў прыклады як для WinForms і WPF.
дададзена аўтар Berin Loritsch, крыніца
@HamunSunga, шкадую пра гэта. Мая галоўная рэкамендацыя складаецца ў тым, каб выкарыстоўваць спецыяльную нітку вы можаце даць імя (варыянт 2). Такім чынам, вы можаце лёгка выявіць, калі вы выпадкова пачалі больш аднаго патоку для кіравання падлучэннямі, калі вы бачыце няўстойлівае паводзіны.
дададзена аўтар Berin Loritsch, крыніца
@KfirGuy, ён павінен рабіць з BroadcastTheConnection() кода, даўно працуе. Акрамя таго, лягчэй вызначыць, калі ў вас ёсць больш чым адзін з іх адбываецца адначасова. Вось чаму я хацеў бы выступаць спецыяльную нітку да гэтага.
дададзена аўтар Berin Loritsch, крыніца
@HamunSunga, калі ваша логіка досыць складаная, вы можаце проста хочаце падоўжыць Тэма і перагружаць Thread.start() метад. Тым не менш, вы можаце выкарыстоўваць ананімныя метады проста выдатна.
дададзена аўтар Berin Loritsch, крыніца
дададзена аўтар Berin Loritsch, крыніца
@KfirGuy, колькі вы можаце зрабіць, перш чым запускаць з задач разьбы? І як вы можаце вызначыць, калі ў вас ёсць адначасова прабегі аднаго і таго ж TCP код, які працуе ў той жа час?
дададзена аўтар Berin Loritsch, крыніца
@BerinLoritsch вы жартуеце? Вы не павінны распаўсюджвацца класамі, як тэма і не можам гэта зрабіць, таму што гэта закрыты клас.
дададзена аўтар Kfir Guy, крыніца
Метад BroadcastConnection выкарыстоўвае цыкл для кіравання ўсіх злучэнняў, таму ён павінен працаваць толькі адзін раз.
дададзена аўтар Kfir Guy, крыніца
асінхронныя і чакаем таксама добра працаваць з доўгімі запушчанымі задачамі. Ён не блакуе карыстацкі інтэрфейс.
дададзена аўтар Kfir Guy, крыніца
Чаму б вам не выкарыстоўваць асінхроннымі і чакаць? Гэта значна прасцей. Праверце мой адказ.
дададзена аўтар Kfir Guy, крыніца
Я шкадую, што я не магу прыйсці, я не маю дастаткова шмат 20 рэпутацыі ... жаль, что .... @ Берын Loritsch
дададзена аўтар Hamun Sunga, крыніца
У вылучаным выкарыстанні Thread, Ананімныя метады не так?
дададзена аўтар Hamun Sunga, крыніца
вялікі дзякуй
дададзена аўтар Hamun Sunga, крыніца
Вялікі дзякуй ...: Я ацаніў ... :)
дададзена аўтар Hamun Sunga, крыніца

Існуе асінхронны варыянт AcceptSocket называецца BeginAcceptSocket , які чакае злучэння асінхронна і пачынае новы струмень для новага сокета, падлучанага. Вам усё роўна прыйдзецца чакаць аперацыі, якая будзе завершана, таму што вы знаходзіцеся ў , а цыкл, але вы можаце выкарыстоўваць гэты час для выкліку Application.DoEvents , гэта будзе дазваляюць інтэрфейс для абнаўлення.

while (ServerRunning)
{
    AsyncHandler handler = delegate(asyncResult)
    {
        //Get the new socket
        Socket socket = myList.EndAcceptSocket(asyncResult);

        //Marshal UI specific code back to the UI thread
        MethodInvoker invoker = delegate()
        {
            listOFClientsSocks.Add(socket);
            listBox1.DataSource = listOFClientsSocks;
            displayText.AppendText("\nConnected");
        };
        listBox1.Invoke(invoker);

        //Call the handler
        tcpHandler();
    }
    IAsyncResult waitResult = myList.BeginAcceptSocket(handler, null);

    //Wait until the async result's wait handle receives a signal
    //Use a timeout to referesh the application every 100 milliseconds
    while (!waitResult.AsyncWaitHandle.WaitOne(100))
    {
        Application.DoEvents();
        if (!ServerRunning)
        {
            break;
        }
    }
}

Рашэнне робіць ваш карыстацкі інтэрфейс рэагуе з адносна змяненняў у вашу структуру коды. Я б, аднак, рэкамендуем перагледзець усю стратэгію выкарыстання TcpListener . Праслухоўванне TCP злучэнняў у вашым UI струмені, як правіла, не з'яўляецца добрай ідэяй. Стварыць спецыяльны клас, які робіць праслухоўванне для вас у асобным патоку і атрымаць доступ да яго з кода карыстацкага інтэрфейсу.

Вы таксама павінны ведаць, што ў прыведзеным вышэй ваш уловам код блок не будзе нічога ўнутры ананімнага дэлегаты, які выкарыстоўваецца BeginAcceptSocket ручка. Я таксама дадаў код, каб спыніць праслухоўванне, калі сервер не працуе больш. Гэта, верагодна, не варта, бо ў гэтым выпадку BeginAcceptSocket згенеруе выключэнне. Ён служыць, хоць у якасці дадатковай гарантыі.

1
дададзена

Найпросты прыклад выкарыстання Thread слухаць і мець зносіны з выдаленым канцом:

public class ListenerThread
{
   //clients list/queue
    Queue m_Clients;
   //thread used to listen for new connections
    Thread m_Thread;
    Socket m_Socket;
    IPEndPoint m_LocalEndPoint;

    volatile bool m_IsListening;

    public ListenerThread(int port)
    {
       //get this machine hostname
        IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());  
       //resolve ip address from hostname
        IPAddress ipAddress = ipHostInfo.AddressList[0];  
       //create local end point object 
        m_LocalEndPoint = new IPEndPoint(ipAddress, port);  
    }

    void Listen()
    {
       //reset clients list
        m_Clients = new Queue();
       //initialize socket
        m_Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp ); 
       //bind this socket to listen for incomming connections to specified end point
        m_Socekt.Bind(localEndPoint);
       //start listening with backlog of 1337 connections
        m_Socket.Listen(1337);  
       //dont forget to dispose after socket was used to "unbind" it
        using ( m_Socket )
        {
            while ( m_IsListening )
            {
               //while listening just accept connections and start them at another thread
                Socket client = m_Socket.Accept();
                if ( client != null )
                {
                    m_Clients.Enqueue(new ClientConnection(client));
                }
            }
        }
    }

   //method used to start the listening server
    public void Start()
    {
        if ( m_Thread == null )
        {
            m_Thread = new Thread(Listen);
        }

        m_IsListening = true;
        m_Thread.Start();
    }

   //method used to stop listening server
    public void Stop()
    {
        m_Listening = false;
        m_Thread.Join();
        while ( m_Clients.Count != 0 )
        {
            m_Clients.Dequeue().Kill();
        }
    }
}

// class used to communicate with the client
public class ClientConnection
{
    Socket m_Socket;//client socket
    Thread m_Thread;//communication thread

    volatile bool m_IsCommunicating;

   //this should start immediately because of the incomming connection priority
    internal ClientConnection(Socket socket)
    {
        m_Socket = socket;
        m_Thread = new Thread(Communicate);
        m_Thread.Start();
    }

   //loop in which you should send/receive data
    void Communicate()
    {
        while ( m_IsCommunicating )
        {
           //.. do your communication stuff
        }
    }

   //should be only used by ListenerThread to end communication.
    internal void Kill()
    {
        m_IsCommunicating = false;
        try
        {
            m_Thread.Join(5 * 1000);
            m_Thread.Abort();
        }
        catch(Exception ex) { /*...*/ }
    }
}

Гэта сапраўды просты прыклад можна, так што вы павінны змяніць гэта для вашых патрэб.
Для таго, каб выкарыстоўваць гэта з вашым прыкладам проста запусціць ListenerThread :

ListenerThread listener = new ListenerThread(8001);
listener.Start();
displayText.AppendText("The server is running at port 8001...\n");

Апошняя рэч, калі вы хочаце, каб зрабіць званкі ў UI, я прапанаваў бы выкарыстаць SynchronizationContext . Для таго, каб зрабіць яго больш ясным ў ListenerThread выкліку гэтага канструктара:

m_Sync = SynchronizationContext.Current;

І зрабіць яшчэ адно поле:

SynchronizationContext m_Sync;

Тады проста перадаць гэты кантэкст у ClientConnection Канструктар як новы ClientConnection (m_Sync, кліент); .

Цяпер вы можаце выкарыстоўваць SynchronizationContext.Post метад, напрыклад. :

m_Sync.Post( state => { someUITextElement.AppendText((string)state); }, "hello world");
1
дададзена

зрабіць ніжэй змен з выкарыстаннем Async і AWAIT

private async void bBroadcast_Click(object sender, EventArgs e) //-- Async
{
    ipAdrsNew = ipBox.Text;
    portNo = Convert.ToInt32(portBox.Text);
    await BroadcastTheConnection();           //-- await
}

public Task BroadcastTheConnection()
{
   return Task.Run(() =>
   {
       //---- code for BroadcastTheConnection 
   });
}
1
дададзена
Такім чынам, заданне няправільна. У C#, правільны спосаб зрабіць асінхронныя праграмаванне з'яўляецца тое, з асінхронным і чакаць. Праверце мой адказ, каб убачыць, наколькі гэта прасцей, чым пры выкарыстанні тэмы або задачы.
дададзена аўтар Kfir Guy, крыніца
Чаму б вам кіраваць задачамі напрамую? Проста выкарыстоўвайце асінхронныя і чакаю.
дададзена аўтар Kfir Guy, крыніца

Вы можаце выкарыстоўваць асінхронныя і чакаць для дасягнення асінхронных сетак у C #.

Паспрабуйце гэта (я таксама перапрацаваны код):

public async Task BroadcastConnectionAsync(IPAddress address, int port)
{
    try
    {
        var listener = new TcpListener(address, port);

        ServerRunning = true;
       //Start Listeneting at the specified port

        listener.Start();
        displayText.AppendText("The server is running at port 8001...\n");

        while (ServerRunning)
        {
            using (var socket = await listener.AcceptSocketAsync())
            {
                listOFClientsSocks.Add(socket);
                listBox1.DataSource = listOFClientsSocks;

                displayText.AppendText("\nConnected");
                new Thread(tcpHandler)
                {
                    Name = "tcpHandler"
                }.Start();
            }
        }
    }
    catch (Exception ex)
    {
        displayText.AppendText("Error----" + Environment.NewLine + ex.StackTrace);
    }
}

І ваш клік апрацоўшчык падзеі:

private async void bBroadcast_Click(object sender, EventArgs e)
{
    var address = IPAddress.Parse(ipBox.Text);
    int port = Convert.ToInt32(portBox.Text);
    await BroadcastConnectionAsync(address, port);
}
1
дададзена
Асінхронны-Await з'яўляецца лепшым і прапанаваў варыянт, хоць ёсць праблема, паколькі падзея з'яўляецца нікчэмным які вяртаецца тып, метад асінхронны павінен таксама быць несапраўдным вяртанне ня задачы. Акрамя таго, пачынаючы яшчэ адзін паток, абнаўленне кіравання Ui ўнутры метаду асінхроннага не зьяўляюцца добрай аперацыяй
дададзена аўтар Mrinal Kamboj, крыніца
Дзякуй, я паспрабую гэта таксама ... :)
дададзена аўтар Hamun Sunga, крыніца

Вы можаце выкарыстоўваць фон рабочыя таксама. Ніжэй прыведзены невялікі прыклад таго ж самага.

private void MainMethod()
        {
            BackgroundWorker bg = new BackgroundWorker();
            bg.DoWork += Bg_DoWork;
            bg.RunWorkerCompleted += Bg_RunWorkerCompleted;

        }

        private void Bg_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            //here do your UI related work like changing the color of label or filling up data in grid etc
        }

        private void Bg_DoWork(object sender, DoWorkEventArgs e)
        {
            //Here do your time consuming work without blocking ui thread
        }
0
дададзена