Laden...

.net 3.5 Console: Warten bis alle n Async-Aufrufe beendet sind

Erstellt von UNeverNo vor 8 Jahren Letzter Beitrag vor 8 Jahren 1.513 Views
UNeverNo Themenstarter:in
153 Beiträge seit 2007
vor 8 Jahren
.net 3.5 Console: Warten bis alle n Async-Aufrufe beendet sind

Ich hab schon ein wenig zum Thema gegoogelt, aber alle Treffer beschäftigen sich irgendwie mit Task als Rückgabeobjekt.

In meinem Fall rufe ich Async-Methoden von ASP.NET, die void als Rückgabetyp haben. Ich möchte nun sicherstellen, dass meine Konsolenanwendung erst dann beendet wird, wenn alle offenen Completed-Events gefeuert wurden.

Aktuell denke ich darüber nach jeden Aufruf über i++ zu inkremieren und jedes Completed-Event dies über i-- dekremieren zu lassen. Der Main-Thread läuft über while (i>0), allerdings erscheint mir das nicht sonderlich sauber zu sein - gibt es da ein einfacheres Handling, was unter VS 2010/.NET 3.5 verfügbar ist?

Grüße,
Chris

"Wenn Architekten genauso bauen würden, wie Programmierer programmieren, dann würde der erste Specht, der vorbeikommt, die Zivilisation zerstören." (Steven Weinberg)

6.911 Beiträge seit 2009
vor 8 Jahren

Hallo UNeverNo,

in .net 4.0 wäre das ein Anwendungsfall für ein CountdownEvent.
Mit async/await ein Fall für Task.WhenAll.

Davor kann ein Konstrukt wie du mit dem Zähler erwähnt hast schon verwendet werden, allerdings nicht dass per while "gewartet" wird, sondern per WaitHandle.

z.B als einfaches Beispiel - sollte für dein Problem reichen, mehr hab ich jetzt nicht gemacht. Funktionieren sollte es, aber getestet hab ich es nicht 😉


using System;
using System.Net;
using System.Threading;

namespace ConsoleApplication1
{
	class Program
	{
		static void Main(string[] args)
		{
			using (WebClient wc1 = new WebClient())
			using (WebClient wc2 = new WebClient())
			using (CountdownEvent cde = new CountdownEvent())
			{
				DownloadStringCompletedEventHandler handler = (s, e) =>
				{
					Console.WriteLine("{0} completed", e.UserState);
					cde.Signal();
				};

				wc1.DownloadStringCompleted += handler;

				wc1.DownloadStringAsync(new Uri("http://www.mycsharp.de"), "mycsharp");
				cde.AddCount();

				wc2.DownloadStringCompleted += handler;
				wc2.DownloadStringAsync(new Uri("http://www.google.at"), "google");
				cde.AddCount();

				Console.WriteLine("Waiting for download...");
				cde.Wait();
				Console.WriteLine("Done");
			}
		}
	}

	public class CountdownEvent : IDisposable
	{
		private int _count;
		private ManualResetEvent _waitHandle = new ManualResetEvent(false);
		//---------------------------------------------------------------------
		public CountdownEvent() : this(0) { }
		public CountdownEvent(int count)
		{
			_count = count;
		}
		//---------------------------------------------------------------------
		public void AddCount()
		{
			this.ThrowIfDisposed();

			Interlocked.Increment(ref _count);
		}
		//---------------------------------------------------------------------
		public void Signal()
		{
			this.ThrowIfDisposed();

			if (Interlocked.Decrement(ref _count) == 0)
				_waitHandle.Set();
		}
		//---------------------------------------------------------------------
		public void Wait()
		{
			this.ThrowIfDisposed();

			_waitHandle.WaitOne();
		}
		//---------------------------------------------------------------------
		#region IDisposable Members
		private bool _isDisposed = false;
		//---------------------------------------------------------------------
		[System.Diagnostics.DebuggerStepThrough]
		protected void ThrowIfDisposed()
		{
			if (_isDisposed) throw new ObjectDisposedException(this.ToString());
		}
		//---------------------------------------------------------------------
		protected virtual void Dispose(bool disposing)
		{
			if (!_isDisposed)
			{
				if (disposing)
					_waitHandle.Close();

				_isDisposed = true;
			}
		}
		//---------------------------------------------------------------------
		[System.Diagnostics.DebuggerStepThrough]
		public void Dispose()
		{
			this.Dispose(true);
			GC.SuppressFinalize(this);
		}
		#endregion
	}
}

Wenn du - wie hier im Beispiel - schon vorab weißt dass es n async-Vorgänge sind, so kann das CountDownEvent gleich mit der Ctor-Überladung für die entsprechende Anzahl erstellt werden.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

UNeverNo Themenstarter:in
153 Beiträge seit 2007
vor 8 Jahren

Vielen Dank für die Antwort. Es hilft mir schon ein wenig weiter, allerdings ist das Verhalten immer noch "komisch".

Die Callbacks werden öfter gerufen als sie sollten - wenn ich 5 Elemente in der Liste habe - 10x.


        private bool Work()
        {
            _waiter = new CountdownEvent2(lstsDBAliasNames.Count);
            foreach (string sName in lstsNames)
            {
                oWS.WMGetDataAsync(sName, sName);
                oWS.WMGetDataCompleted += new WMGetDataCompletedEventHandler(oWS_WMGetDataCompleted);
            }
            _waiter.Wait();

            return true;
        }

        void oWS_WMGetDataCompleted(object sender, WMGetDataCompletedEventArgs e)
        {
            string sDaten = e.Result;
			string sName = e.UserState;
            _waiter.Signal();
        }

Ich bin ein wenig ratlos - es scheint definitiv etwas mit der Synchronität zu tun zu haben - aber was? Im Work dürfte es eigentlich keine Probleme geben, da ja niemand parallel darauf zugreift.

Ich dachte dann, dass es Probleme mit parallelen Callbacks geben könnte, was eigentlich auch keinen Sinn gibt - und ein lock drum herum änderte auch nichts am Ergebnis - hier mal ein Log (Liste: Name1,Name2,Name3,Name4,Name5):


foreach Name1
foreach Name2
foreach Name3
foreach Name4
foreach Name5
callback Name1
callback Name1
callback Name1
callback Name1
callback Name1
callback Name4
callback Name4
callback Name4
callback Name4
callback Name4

Oder brauche ich am Log eine Synchronisierung?

Grüße,
Chris

"Wenn Architekten genauso bauen würden, wie Programmierer programmieren, dann würde der erste Specht, der vorbeikommt, die Zivilisation zerstören." (Steven Weinberg)

6.911 Beiträge seit 2009
vor 8 Jahren

Hallo UNeverNo,

du hast immer das Gleich oWS-Objekt und somit brauchst du nur 1x den Event-Handler anhängen. So hängst du diesen in jedem Schleifendurchlauf neu an und daher wird der Handler auch öfters aufgerufen. Siehe auch [FAQ] Eigenen Event definieren / Information zu Events (Ereignis/Ereignisse)

BTW: schau dir einaml Naming Guidelines an

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"