programing

백그라운드 작업자를 사용하여 GUI를 업데이트하는 방법은 무엇입니까?

javamemo 2023. 5. 15. 20:59
반응형

백그라운드 작업자를 사용하여 GUI를 업데이트하는 방법은 무엇입니까?

저는 제 애플리케이션이 스레드를 사용하도록 하루 종일 노력했지만, 운이 없었습니다.저는 그것에 대한 문서를 많이 읽었고 아직도 많은 오류가 있으니 당신이 저를 도와주시길 바랍니다.

데이터베이스를 호출하고 GUI를 업데이트하는 큰 시간이 걸리는 방법이 있습니다.이는 항상(또는 약 30초마다) 발생해야 합니다.

public class UpdateController
{
    private UserController _userController;

    public UpdateController(LoginController loginController, UserController userController)
    {
        _userController = userController;
        loginController.LoginEvent += Update;
    }

    public void Update()
    {
        BackgroundWorker backgroundWorker = new BackgroundWorker();
        while(true)
        {
            backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
            backgroundWorker.RunWorkerAsync();
        }     
    }

    public void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        _userController.UpdateUsersOnMap();
    }
}

이 접근 방식에서는 백그라운드 작업자가 STA 스레드가 아니기 때문에 예외가 발생합니다(그러나 제가 이해하기로는 이것이 제가 사용해야 하는 것입니다).STA 스레드로 시도해 보았는데 다른 오류가 발생했습니다.

(백그라운드 스레드에서) 데이터베이스 호출을 하면서 GUI를 업데이트하려고 하기 때문에 문제가 있는 것 같습니다.데이터베이스 호출만 하면 어떻게든 메인 스레드로 전환됩니다.주 스레드가 실행된 후에는 백그라운드 스레드 등으로 돌아갑니다.하지만 어떻게 하는지 모르겠어요.

애플리케이션은 데이터베이스 호출 직후 GUI를 업데이트해야 합니다.파이어링 이벤트가 효과가 없는 것 같습니다.백그라운드 스레드가 입력됩니다.

편집:

몇 가지 정말 훌륭한 답변 :) 이것은 새로운 코드입니다:

public class UpdateController{
private UserController _userController;
private BackgroundWorker _backgroundWorker;

public UpdateController(LoginController loginController, UserController userController)
{
    _userController = userController;
    loginController.LoginEvent += Update;
    _backgroundWorker = new BackgroundWorker();
    _backgroundWorker.DoWork += backgroundWorker_DoWork;
    _backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
}

public void _backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    _userController.UpdateUsersOnMap();
}

public void Update()
{   
    _backgroundWorker.RunWorkerAsync();
}

void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    //UI update
    System.Threading.Thread.Sleep(10000);
    Update();
}

public void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    // Big database task
}

}

하지만 어떻게 하면 10초마다 실행할 수 있을까요?시스템.스레드화.Thread.Sleep(10000)은 내 GUI를 고정시키고 제안된 대로 Update()의 (true) 루프는 예외를 제공합니다(스레드가 너무 사용됨).

BackgroundWorker를 한 번 선언하고 구성해야 합니다. 그런 다음 루프 내에서 RunWorkerAsync 메서드를 호출합니다.

public class UpdateController
{
    private UserController _userController;
    private BackgroundWorker _backgroundWorker;

    public UpdateController(LoginController loginController, UserController userController)
    {
        _userController = userController;
        loginController.LoginEvent += Update;
        _backgroundWorker = new BackgroundWorker();
        _backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
        _backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
        _backgroundWorker.WorkerReportsProgress= true;
    }

    public void Update()
    {
         _backgroundWorker.RunWorkerAsync();    
    }

    public void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        while (true)
        {
        // Do the long-duration work here, and optionally
        // send the update back to the UI thread...
        int p = 0;// set your progress if appropriate
        object param = "something"; // use this to pass any additional parameter back to the UI
        _backgroundWorker.ReportProgress(p, param);
        }
    }

    // This event handler updates the UI
    private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        // Update the UI here
//        _userController.UpdateUsersOnMap();
    }
}

컨트롤을 사용해야 합니다.Required 속성을 호출하여 백그라운드 스레드에 있는지 확인합니다.그런 다음 컨트롤을 통해 UI를 수정한 논리를 호출해야 합니다.메서드를 호출하여 UI 작업을 메인 스레드에서 수행합니다.대리자를 작성하여 컨트롤에 전달하면 됩니다.메서드를 호출합니다.여기서 중요한 점은 이러한 메서드를 호출하려면 Control에서 파생된 개체가 필요하다는 것입니다.

편집: 다른 사용자가 게시한 것처럼 BackgroundWorker를 기다릴 수 있는지 여부.UI를 업데이트하는 이벤트가 완료되었습니다. 그러면 해당 이벤트에 가입하고 UI 코드를 직접 호출할 수 있습니다.기본 앱 스레드에서 BackgroundWorker_Completed가 호출됩니다. 내 코드는 작업 중에 업데이트를 수행할 것으로 가정합니다.제 방법의 한 가지 대안은 BwackgroundWorker에 가입하는 것입니다.ProgressChanged 이벤트가 발생했지만, 이 경우 UI를 업데이트하려면 Invoke를 호출해야 합니다.

예를들면

public class UpdateController
{
    private UserController _userController;        
    BackgroundWorker backgroundWorker = new BackgroundWorker();

    public UpdateController(LoginController loginController, UserController userController)
    {
        _userController = userController;
        loginController.LoginEvent += Update;
    }

    public void Update()
    {                        
         // The while loop was unecessary here
         backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
         backgroundWorker.RunWorkerAsync();                 
    }

    public delegate void DoUIWorkHandler();


    public void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
       // You must check here if your are executing on a background thread.
       // UI operations are only allowed on the main application thread
       if (someControlOnMyForm.InvokeRequired)
       {
           // This is how you force your logic to be called on the main
           // application thread
           someControlOnMyForm.Invoke(new             
                      DoUIWorkHandler(_userController.UpdateUsersOnMap);
       }
       else
       {
           _userController.UpdateUsersOnMap()
       }
    }
}

무한 이벤트 핸들러를 추가하고 무한 이벤트 핸들러를 호출하는 동안(참) 제거해야 합니다.

백그라운드 작업이 완료된 경우 backgroundWorker 클래스에서 RunWorkerCompleted 이벤트를 사용하여 수행할 작업을 정의할 수 있습니다.따라서 DoWork 처리기에서 데이터베이스 호출을 수행한 다음 RunWorkerCompleted 처리기에서 인터페이스를 업데이트해야 합니다.

BackgroundWorker bgw = new BackgroundWorker();
bgw.DoWork += (o, e) => { longRunningTask(); }

bgw.RunWorkerCompleted += (o, e) => {
    if(e.Error == null && !e.Cancelled)
    {
        _userController.UpdateUsersOnMap();
    }
}

bgw.RunWorkerAsync();

이전에 언급한 내용 외에도, www.albahari.com/threading 에서 찾을 수 있는 최고의 스레딩 문서를 살펴보십시오.BackgroundWorker를 올바르게 사용하는 방법을 알려줍니다.

BackgroundWorker가 Completed 이벤트(Completed 이벤트를 실행하면 쉽게 제어할 수 있도록 UI 스레드에서 호출됨)를 실행할 때 GUI를 업데이트해야 합니다.자신을 호출합니다.).

다음은 일부 WinForms 예제 코드를 기반으로 사용할 수 있는 소스 코드 패턴이지만 WPF에도 매우 쉽게 적용할 수 있습니다.이 예에서는 출력을 콘솔로 리디렉션하여 백그라운드 작업자가 처리하는 동안 텍스트 상자에 메시지를 쓸 수 있도록 합니다.

구성 요소:

  • 도우미 클래스TextBoxStreamWriter콘솔 출력을 텍스트 상자로 리디렉션하는 데 사용됨
  • 백그라운드 작업자가 리디렉션된 콘솔에 쓰기
  • 백그라운드 작업 완료 후 재설정해야 하는 진행률 표시줄
  • 일부 텍스트 상자(txtPath 및 txtResult) 및 "시작" 버튼

즉, UI와 상호 작용해야 하는 몇 가지 백그라운드 작업이 있습니다.이제 그것이 어떻게 이루어지는지 보여드리겠습니다.

백그라운드 작업의 컨텍스트에서 를 사용하여 UI 요소에 액세스해야 합니다.그렇게 하는 가장 간단한 방법은 람다 표현 구문을 사용하는 것이라고 생각합니다.

progressBar1.Invoke((Action) (() =>
    {   // inside this context, you can safely access the control
        progressBar1.Style = ProgressBarStyle.Continuous;
    }));

다음과 같은 로컬 방법으로 진행률 표시줄을 업데이트하려면

private void UpdateProgress(int value)
{
    progressBar1.Invoke((Action)(() => { progressBar1.Value  = value; }));
}

도움을 줍니다. 그것은 지나갑니다.value진행률 표시줄에 대한 매개 변수를 폐쇄로 지정합니다.


콘솔 출력을 리디렉션하는 데 사용되는 도우미 클래스 TextBoxStreamWriter입니다.

public class TextBoxStreamWriter : TextWriter
{

    TextBox _output = null;

    public TextBoxStreamWriter(TextBox output)
    {
        _output = output;
    }

    public override void WriteLine(string value)
    {
        // When character data is written, append it to the text box.
        // using Invoke so it works in a different thread as well
        _output.Invoke((Action)(() => _output.AppendText(value+"\r\n")));
    }

}
        

로드 이벤트에서 다음과 같이 사용해야 합니다(위치:txtResult는 출력이 리디렉션되는 텍스트 상자입니다).

private void Form1_Load(object sender, EventArgs e)
{
    // Instantiate the writer and redirect the console out
    var _writer = new TextBoxStreamWriter(txtResult);
    Console.SetOut(_writer);
}

또한 양식에는 백그라운드 작업자를 시작하는 버튼이 있으며, 이 버튼은 해당 작업자에게 경로를 전달합니다.

private void btnStart_Click(object sender, EventArgs e)
{
    backgroundWorker1.RunWorkerAsync(txtPath.Text);
}

이것은 백그라운드 작업자의 작업량입니다. 콘솔을 사용하여 텍스트 상자에 메시지를 출력하는 방법(이전에 설정한 리디렉션 때문)을 기록하십시오.

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    var selectedPath = e.Argument as string;
    Console.Out.WriteLine("Processing Path:"+selectedPath);
    // ...
}

selectedPath에 전달된 경로로 구성됩니다.backgroundWorker1 변수 " 미터를통이전에해파라"를 txtPath.Text를 통해 액세스되고 있습니다.e.Argument.

나중에 일부 컨트롤을 재설정해야 하는 경우 위에서 이미 언급한 대로 다음 방법으로 재설정합니다.

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    progressBar1.Invoke((Action) (() =>
        {
            progressBar1.MarqueeAnimationSpeed = 0;
            progressBar1.Style = ProgressBarStyle.Continuous;
        }));
}

이 예에서는 완료 후 진행률 표시줄이 재설정됩니다.


중요:GUI 컨트롤에 액세스할 때마다 위의 예제에서와 같이 사용합니다.코드에서 볼 수 있듯이 람다를 사용하면 쉽게 사용할 수 있습니다.


다음은 LinkqPad 6에서 실행되는 완전한 예제입니다(빈 C# 프로그램 쿼리에 복사하여 붙여넣기만 하면 됩니다). 여러분 모두 Visual Studio에서 새로운 Windows Forms 프로젝트를 만드는 방법을 알고 있기 때문에 새로운 것을 배울 수 있도록 이번에는 LinkqPad를 사용하기로 결정했습니다(그리고 여전히 그렇게 하고 싶다면).아래 이벤트를 복사하고 컨트롤을 폼으로 드래그 앤 드롭하기만 하면 됩니다.)

// see: https://stackoverflow.com/a/27566468/1016343

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

BackgroundWorker backgroundWorker1 = new System.ComponentModel.BackgroundWorker();
ProgressBar progressBar1 = new ProgressBar() { Text = "Progress", Width = 250, Height=20, Top=10, Left=0 };
TextBox txtPath = new TextBox() { Text =@"C:\temp\", Width = 100, Height=20, Top=30, Left=0 };
TextBox txtResult = new TextBox() { Text = "", Width = 200, Height=250, Top=70, Left=0, Multiline=true, Enabled=false };
Button btnStart = new Button() { Text = "Start", Width = 100, Height=30, Top=320, Left=0 };

void Main()
{
    // see: https://www.linqpad.net/CustomVisualizers.aspx

    // Instantiate the writer and redirect the console out
    var _writer = new TextBoxStreamWriter(txtResult);
    Console.SetOut(_writer);
    
    // wire up events
    btnStart.Click += (object sender, EventArgs e) => btnStart_Click(sender, e);
    backgroundWorker1.DoWork += (object sender, DoWorkEventArgs e) => backgroundWorker1_DoWork(sender, e);
    backgroundWorker1.RunWorkerCompleted += (object sender, RunWorkerCompletedEventArgs e)
                                  => backgroundWorker1_RunWorkerCompleted(sender, e);
    using var frm = new Form() {Text="Form", Width = 300, Height=400, Top=0, Left=0};
    frm.Controls.Add(progressBar1);
    frm.Controls.Add(txtPath);
    frm.Controls.Add(txtResult);
    frm.Controls.Add(btnStart);
    
    // display controls
    frm.ShowDialog();
}

private void btnStart_Click(object sender, EventArgs e)
{
    backgroundWorker1.RunWorkerAsync(txtPath.Text);
}

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    InitProgress();
    var selectedPath = e.Argument as string;
    Console.Out.WriteLine("Processing Path: " + selectedPath);
    UpdateProgress(0); Thread.Sleep(300); UpdateProgress(30); Thread.Sleep(300); 
    UpdateProgress(50); Thread.Sleep(300); 
    Console.Out.WriteLine("Done.");
    
    // ...
}

private void UpdateProgress(int value)
{
    progressBar1.Invoke((Action)(() =>
       {
           progressBar1.Value  = value;
       }));
}

private void InitProgress()
{
    progressBar1.Invoke((Action)(() =>
       {
           progressBar1.MarqueeAnimationSpeed = 0;
           progressBar1.Style = ProgressBarStyle.Continuous;
       }));
}

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    UpdateProgress(100); // always show 100% when done
}

// You can define other methods, fields, classes and namespaces here
public class TextBoxStreamWriter : TextWriter
{

    TextBox _output = null;

    public TextBoxStreamWriter(TextBox output)
    {
        _output = output;
    }

    public override Encoding Encoding => throw new NotImplementedException();

    public override void WriteLine(string value)
    {
        // When character data is written, append it to the text box.
        // using Invoke so it works in a different thread as well
        _output.Invoke((Action)(() => _output.AppendText(value + "\r\n")));
    }

}

@Lee의 답변에 있는 if-statement는 다음과 같아야 합니다.

bgw.RunWorkerCompleted += (o, e) => {
    if(e.Error == null && !e.Cancelled)
    {
        _userController.UpdateUsersOnMap();
    }
}

이...만약 당신이 호출하고 싶다면.UpdateUsersOnMap();오류가 없고 BgWorker가 취소되지 않은 경우.

언급URL : https://stackoverflow.com/questions/1862590/how-to-update-gui-with-backgroundworker

반응형