C# WinForms + Threads

Zhen

Lt. Junior Grade
Registriert
Aug. 2009
Beiträge
299
Hallo Leute,

wiedermal muss ich mich an die CB-Gemeinde wenden, denn mein derzeitiges Problem hat mir gerade beinahe auch den letzten Nerv geraubt :freak:

Folgendes Szenario:

PHP:
class MyClass1 : IMyInterface{
  private MyClass2 meins;

  public void Function1() {
    ...
  }

  public void Function2(int parameter) {
    ...
  }

  private void Function3(int parameter1, int parameter2) {
    ...
  }
}


PHP:
class MyClass2 {
  public MyClass2() {
    ...
  }

  public void Function1(int parameter) {
    ...
  }

  public void Function2() {
    ...
  }

  private void Function3(int parameter1, int parameter2) {
    ...
  }

  public event MyEventHandler OnThisEvent;

  public event MyEventHandler2 OnThatEvent;
}

MyClass1 ist die Klasse die als WCF Dienst läuft. MyClass2 wird von MyClass1 verwendet und auch innerhalb dieser Instanziert, aber ist eine ganz eigenständige Klasse.

Wir ihr sehen könnt hat aber MyClass2 einige Events. Auf diese Events möchte ich innerhalb von MyClass1 reagieren (dazu abonniere ich diese: meins.OnThisEvent += ....).

Bis dahin klappts ja wunderbar, die Events werden aufgerufen, die Funktionen darinen abgearbeitet, aber sobald ich z.B. eine Form innerhalb eines dieser Events einbaue und diese öffne, dann hängt sie sich auf. Ich kann die Form weder bedienen noch sonst irgendwas mit ihr machen. Ich kann dann nur das ganze Programm abschießen!

Ich habs auch schon mit InvokeRequired und Invoke probiert, aber es ist immer das selbe. Das Problem ist aber, dass ich innerhalb von einem dieser Events unbedingt die Form brauche und diese von dort aus auch manipulieren muss.

Um genau zu sein, geht es um eine ProgressBar. Ich will dem User anzeigen können wie weit der gerade ausgeführte Prozess bereits ist. Wie gesagt, Invoke hat mir da leider nicht geholfen... die Form hängt sich, ab dem Moment in dem ich "meineForm.Show()" aufrufe, auf!


Hoffe wirklich ihr könnt mir da weiterhelfen. Mir sind die Ideen nämlich endgültig ausgegangen wie ich diese verflichste ProgressBar dem User anzeigen kann...
 
Tipp: Backgroundworker

Wird üblicherweise benutzt um Progress upzudaten...

Zum Problem:
Ruf das Fenster mal in einem eigenen neuen Thread auf....

EDIT:
Poste mal den Code, der in dem Fenster ausgeführt wird und wie das Fenster geöffnet wird.
 
Zuletzt bearbeitet:
Folgenden Code benutze ich im WCF Service um das Fenster aufzurufen und die ProgressBar upzudaten:

PHP:
[ServiceBehavior()]
internal class myHostClass : ImyHostService, IDisposable {
  
  private Form progressForm; // das Formular dass die ProgressBar enthält

  private ProgressBar progressBar; // die ProgressBar die dem User den Stand der Dinge anzeigt

  private MyLib myLib; // meine DLL die einen bestimmten Prozess ausführt


  // Funktion die vom Host aufgerufen wird, ist auch im Interface so aufgelistet
  public void Initialize() {
    myLib = new MyLib();

    myLib.OnStart += new StartEventHandler(myLib_OnStart);
    myLib.OnUpdate += new UpdateEventHandler(myLib_OnUpdate);
  }

  bool myLib_OnStart(object sender, StartEventArgs e) {
    CreateProgressForm();

    return true;
  }

  void myLib_OnUpdate(object sender, UpdateEventArgs e) {
    if(progressForm.InvokeRequired) {
      progressForm.Invoke(new UpdateEventHandler(myLib_OnUpdate), new object[] { sender, e });
    } else {
      progressBar.Value = e.Percent;
      progressBar.Refresh();
    }
  }

  private void CreateProgressForm() {
    progressForm = new Form();
    progressBar = new ProgressBar();

    progressForm.Width = 300;
    progressForm.Height = 80;
    progressForm.MaximizeBox = false;
    progressForm.FormBorderStyle = FormBorderStyle.FixedSingle;
    progressForm.Controls.Add(progressBar);

    progressBar.Width = 268;
    progressBar.Height = 23;
    progressBar.Location = new Point(12, 12);

    progressForm.Show();
  }
}

Im Fenster selbst wird kein anderer Code mehr ausgeführt, nur das was ihr hier seht ;)


Habs auch schon mit folgendem Probiert:

PHP:
  bool myLib_OnStart(object sender, StartEventArgs e) {
    Thread thread = new Thread(new ThreadStart(CreateProgressForm));
    thread.Start();

    return true;
  }

In diesem Fall hängt sich zwar das Fenster nicht auf, aber es bleibt auch nicht erhalten. Wird nur kurz (für paar ms) eingeblendet und dann ist es weg, aber dafür läuft der Prozess immerhin ab wie er soll.


PS:
Die Methoden "CreateProgressForm" und die Events sind mit meinen im richtigen Code identisch. Nur die Klassen selbst und die Methode "Initialize" wurde halt aufs nötigste beschränkt ;) :D

Die restlichen Methoden in der Klasse haben haben keinen Einfluss auf die bereits erwähnten. Also es ist jetzt nicht so, dass die anderen Funktionen vielleicht Schuld an der Misere wären :D :D :D


EDIT:
Ach ja nur nebenbei, aber wahrscheinlich habt ihr es eh schon vermutet, der Event "OnUpdate" wird sehr oft und sehr schnell ausgelöst.
 
Zuletzt bearbeitet:
Also an dem Code würde ich eigentlich nichts besonderes sehen:

PHP:
MyService = new ServiceHost(typeof(myHostClass)); //MyService host wird global in einer Klasse mit "ServiceHost MyService = null;" deklariert.

BindingConfiguration(); // hier werden einfach nur die Einstellungen für das TcpBinding erstellt

MyService.AddServiceEndpoint(typeof(ImyHostService), tcp, "net.tcp://" + ipadresse + ":1369/service");

MyService.Open();

Der obige Code wird ausgeführt sobald man auf einen Button im Hauptfenster klickt.
Ergänzung ()

@metadings:
Danke für den Link, werde ich mir auf jeden Fall anschauen. Dachte aber bis gestern noch ich hätte den Dreh mit den Threads eigentlich rauß xDD :D :D
 
Also, wenn das Progress-Fenster sowieso die ganze Zeit angezeigt werden soll, während die Aufgaben erledigt werden sollen, dann packt doch erst mal die ganze "myLib"-Geschichte in das ProgressForm.
Das zeigst du dann wie gewohnt über .Show() an und über eine neue Methode .StartProcess() oder so beginnst du die myLib-Kommunikation.

Dann wird aber das ProgressForm-Fenster einfrieren, wenn ein onUpdate-Event reinkommt, dafür dann dort ein Backgroundworker-Konstrukt bauen.
 
Ok, ich glaub nun verstehe ich, was du vor hast.
Du musst deinen Code wie folgt ändern:

Das Form, dass du zeigen möchtest:
Code:
public partial class Form1 : Form
{
    public SynchronizationContext SyncContext { get; private set; }

    public Form1()
    {
        InitializeComponent();
        SyncContext = SynchronizationContext.Current;
    }
}

Dein Service:
Code:
[ServiceContract]
public interface IService1
{
    [OperationContract]
    int DoSomething();
}

Code:
public class Service1 : IService1
{
    private Form1 _form;

    #region Implementation of IService1

    public int DoSomething()
    {
        using ( WaitHandle handle = new AutoResetEvent( false ) )
        {
            Thread thread = new Thread( h =>
                {
                    _form = new Form1();
                    ((AutoResetEvent) h).Set();
                    Application.Run( _form );
                } );

            thread.Start( handle );
            handle.WaitOne();
            DoMoreWork();
        }

        return 1;
    }

    private void DoMoreWork()
    {
        for( int i = 0; i< 100; ++i )
        {
            int value = i;
            SendOrPostCallback callback = delegate { _form.progressBar.Value = value; };
            _form.SyncContext.Send( callback, null );
            Thread.Sleep( 1000 );
        }
    }

    #endregion
}

Alternativ zu dem Feld _form kannst du auch mit Application.OpenForms arbeiten.

Wenn _form geschlossen wird, endet der Thread thread. Falls DoMoreWork zu diesem Zeitpunkt noch nicht beendet ist, gibt es eine Exception. Das musst du noch synchronisieren (ist mir eben erst eingefallen ;)).

progressBar ist eine Control von Form1. Du musst hier von private auf mindestens internal wechseln.

Des Weiteren solltest du dich ein wenig über SynchronizationContext und SendOrPostCallback schlau lesen ;)
Ergänzung ()

Eins ist mir grade noch so durch den Kopf gegangen: wozu WCF?
Ich meine, was du da vorhast, funktioniert nur, wenn Host und Client auf dem gleichen Rechner in der gleichen Session laufen.
In diesem Fall macht WCF eigentlich überhaupt keinen Sinn. Stattdessen kannst du auch den Progress, wie Erdmännchen schon schreibt, mit einem Backgroundworker generieren und in der App anzeigen.
 
Naja WCF deswegen, weil das ganze eigentlich ein umfangreicheres Client-Host-Programm ist.

Ich habe halt eben dieses Tool dass auf den Clients läuft und ein 2. das z.B. bei mir (dem Admin) läuft.

Damit kann ich verschiedene Sachen anstellen auf den Clients (bestimmte Aktionen halt) und da gehört eben die Dateiübertragung dazu. Jetzt hab ich aber die Dateiübertragung über TcpClient realisiert und das ganze in eine eigene DLL gepackt (falls ich die mal iwann auch wo anders brauchen werde :rolleyes: ).

Das OnStart event wird aufgerufen, wenn gerade eine Datei eingeht und OnUpdate wird jedes mal aufgerufen wenn ein Stück der Datei übertragen wurde. UpdateEventArgs enthält dann den Prozentual übertragenen Teil und der soll dann der Value-Eigenschaft von der ProgressBar zugewiesen werden...

...zumindestens wenn ich es mal schaffen sollte die ProgressBar mit der Form endlich hinzubekommen xDD
 
D.h. auf den Client-Rechner läut der WCF Host (im Kontext des angemeldeten Users) und du bist als Admin der Client?
 
dem Client-Rechner läuft der WCF Host - ja.
Ich als Admin kann mich mit meinem Tool dann von meinem Rechner aus (fuktioniert halt bisher nur im Intranet) sich zum WCF Host verbinden.

Also es sind zwei Tools. Eins läuft auf den Client-Rechnern im Netzwerk und das 2. bei mir auf dem Admin PC. Der User kann dann per Klick auf einen Button den WCF Dienst starten und ich kann mich dann mit meinem Tool draufschalten.

Es ist halt eine Fernwartung :D :D
+ Dateiübertragung (doch die Dateiübertragung ist per TcpClient/-Listener gelöst statt WCF).
 
Dein Konzept ist mir irgendwie umheimlich ;)

Zhen schrieb:
Es ist halt eine Fernwartung :D :D
+ Dateiübertragung (doch die Dateiübertragung ist per TcpClient/-Listener gelöst statt WCF).

Darf ich fragen, was du vorhast zu warten?
Windows hat mit WMI bereits diese Funktionalität von Haus aus dabei.
 
Naja eigentlich hat ja alles wunderbar funktioniert (auch vom Konzept her) bis auf eben dieses kleine Problem mit der ProgressBar.

Fernwartung im Sinne von TeamViewer oder RemoteDesktop ;) :)
 
Naja, du wirst schon wissen, was du tust :D
Den Code für die ProgressBar habe ich dir oben schon gepostet.
 
Yo ich habs bereits hingekriegt :)

Vielen Dank für deine Hilfe. Hab aber einen einfacheren Weg gefunden :D

Allem Anschein nach hat die Form nur dann rumgesponnen, wenn ich sie ausgerechnet im "OnStart" Event aufgerufen habe. Aber wenn man sie z.B. in der "Initialize()" funktion aufrief, dann lief alles super.

Jetzt hab ich jedenfalls einfach nur eine kleine Funktion dazu aufgenommen die mir die Form erstellt und anzeigt BEVOR die Events der Dateiübertragungsklasse ausgelöst werden ;)


EDIT:
aber mit den zwei Klassen die du mir genannt hast werd ich mich trotzdem beschäftigen. Wird bestimmt nicht schaden :D
 
Zuletzt bearbeitet:
Zurück
Oben