Laden...

Zwei Threads greifen auf Methode zu - Wie verhindere ich das?

Erstellt von R3turnz vor 7 Jahren Letzter Beitrag vor 7 Jahren 2.886 Views
R
R3turnz Themenstarter:in
125 Beiträge seit 2016
vor 7 Jahren
Zwei Threads greifen auf Methode zu - Wie verhindere ich das?

Hallo,
ich habe eine Methode, die bytes asynchron herunterlädt:


        protected async Task<Tuple<bool, byte[]>> DownloadDataAsync(Uri queryURI)
        {
            bool success = true;
            byte[] queryResponse = null;
            var query = new TaskCompletionSource<byte[]>();
            DownloadDataCompletedEventHandler downloadCompleted = (sender, e) => {
                if (e.Error != null)
                    query.SetException(e.Error);
                else
                    query.SetResult(e.Result);
            };
            _client.DownloadDataCompleted += downloadCompleted;
            _client.DownloadDataAsync(queryURI);
            try
            {
                queryResponse = await query.Task;
            }
            catch (WebException)
            {
                success = false;
            }
            _client.DownloadDataCompleted -= downloadCompleted;
            return new Tuple<bool, byte[]>(success, queryResponse);
        }

Wenn ich jetzt in einer Schleife die Methode Aufrufe wird folgende Exception geworfen.


                Forecast = await Task.WhenAll(WeatherParser.ParseForecast(forecastResponse.Item2, Settings.TempUnit).Select(async (forecastAtDay) =>
                {
                    var forecastIcon = await weatherQuery.TryGetIconAsync(forecastAtDay.IconIdentifier);
                    return new WeatherIconWrapper(forecastAtDay, forecastIcon.Item1 ? LoadImage(forecastIcon.Item2) : null);
                }
                ));

Fehlermeldung:
System.InvalidOperationException wurde nicht behandelt.
HResult=-2146233079
Message=Es wurde versucht, eine Aufgabe in einen finalen Zustand zu versetzen, als sie bereits abgeschlossen war.
Source=mscorlib
StackTrace:
bei System.Threading.Tasks.TaskCompletionSource`1.SetResult(TResult result)
bei OpenWeather.DataClient.Query.<>c__DisplayClass2_0.<DownloadDataAsync>b__0(Object sender, DownloadDataCompletedEventArgs e) in Query.cs:Zeile 33.
bei System.Net.DownloadDataCompletedEventHandler.Invoke(Object sender, DownloadDataCompletedEventArgs e)
bei System.Net.WebClient.OnDownloadDataCompleted(DownloadDataCompletedEventArgs e)
bei System.Net.WebClient.DownloadDataOperationCompleted(Object arg)
bei System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
bei System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
bei System.Windows.Threading.DispatcherOperation.InvokeImpl()
bei System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
bei System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
bei MS.Internal.CulturePreservingExecutionContext.Run(CulturePreservingExecutionContext executionContext, ContextCallback callback, Object state)
bei System.Windows.Threading.DispatcherOperation.Invoke()
bei System.Windows.Threading.Dispatcher.ProcessQueue()
bei System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
bei MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
bei MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
bei System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
bei System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
bei System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
bei MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
bei MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
bei System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
bei System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
bei System.Windows.Application.RunDispatcher(Object ignore)
bei System.Windows.Application.RunInternal(Window window)
bei System.Windows.Application.Run(Window window)
bei System.Windows.Application.Run()
bei OpenWeather.GUI.App.Main() in \App.g.cs:Zeile 0.
bei System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
bei System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
bei Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
bei System.Threading.ThreadHelper.ThreadStart_Context(Object state)
bei System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
bei System.Threading.ThreadHelper.ThreadStart()
InnerException:

So weit wie ich das verstehe wird die Methode aus einem anderen Thread schon aufgerufen bevor diese Methode überhaupt beendet wurde. Hier dürfte also nur ein Thread auf die Methode zugreifen. Ist soetwas mit async/await überhaupt möglich oder sehe ich den Fehler an der falschen Stelle?

16.842 Beiträge seit 2008
vor 7 Jahren

Wenn man mit mehreren Threads arbeiten will, dann muss man wissen, was man macht.
Mir sieht das hier nach einem konzeptionellen Fehler aus.
Du solltest die Grundlagen zur [Artikel] Multi-Threaded Programmierung im Schlaf runter singen können, um entsprechend sicher damit umzugehen.
Async/Await ist zwar nicht gleichzusetzen mit paralleler Programmierung; in .NET sind die Basics aber identisch.

Wie sieht denn Dein Code wirklich aus?
Du solltest Fehler nicht unterdrücken oder mit true/false kaschieren, sondern auch bei async/await entsprechend Exceptions durchreichen.
So sagst Du zwar, dass etwas schief ging, aber nicht was. Der berühmte "Es ist ein Fehler aufgetaucht"-Dialog, den kein Mensch will.

Tipp: arbeite mit einem englischen System, sodass Du auch englische Fehlermeldungen bekommst.
Damit kommst Du bei Google und Co viel weiter als mit deutschen Fehlermeldungen (die manchmal haarsträubend übersetzt sind).

R
R3turnz Themenstarter:in
125 Beiträge seit 2016
vor 7 Jahren

Ich würde nicht sagen dass ich es im Schlaf singen kann, aber es ist mir nicht unvertraut. Das war eigentlich der komplette Sourcecode, den ich für wichtig finde. Dazwischen liegt in diesem Fall eigentlich nur eine weitere Methode:


public class Query : IDisposable
{
...
        protected readonly WebClient _client;
        protected async Task<Tuple<bool, byte[]>> DownloadDataAsync(Uri queryURI)
        {
            bool success = true;
            byte[] queryResponse = null;
            var query = new TaskCompletionSource<byte[]>();
            DownloadDataCompletedEventHandler downloadCompleted = (sender, e) => {
                if (e.Error != null)
                    query.SetException(e.Error);
                else
                    query.SetResult(e.Result);
            };
            _client.DownloadDataCompleted += downloadCompleted;
            _client.DownloadDataAsync(queryURI);
            try
            {
                queryResponse = await query.Task;
            }
            catch (WebException)
            {
                success = false;
            }
            _client.DownloadDataCompleted -= downloadCompleted;
            return new Tuple<bool, byte[]>(success, queryResponse);
        }
....
}
public class WeatherQuery : Query,IDisposable
    {
        public async Task<Tuple<bool,byte[]>> TryGetIconAsync(string iconIdentifier)
        {
            return await DownloadDataAsync(BuildWeatherQueryURL("img/w/" + iconIdentifier, null, false));
        }
    }

Bei der normalen Wetterabfrage verwende ich statt dem bool ein Enum, welches dann in eine Fehlermeldung konvertiert wird:


        public enum RequestStatus { OK, InvalidCity, InvalidKey, Failed, UnkownError }

Für mich ist es ein akzeptieble genau Meldung und ich muss die Exception nicht an zwei Stellen mit dem gleichen Code fangen und dort wird es doch ein gutes Stück lesbarer.
Bei den Icons kann ja nur die Verbindung nicht funktionieren.

16.842 Beiträge seit 2008
vor 7 Jahren

Mehrere Punkte:

  • WebClient ist nicht Thread-Safe, sodass eine Instanzvariable kein sinn macht. Zwei mal die Methode aufrufen, ohne, dass die erste Methode fertig ist, führt zur Exception
  • Das await query.Task kann hier in diesem Fall zum Deadlock führen.
  • Wenn eine Methode nichts anderes macht als einen Task auszuführen, kann async / await weggelassen werden; spart ein zusätzliches Task Wrapping
  • Exceptions macht man nicht mit true/false
  • Events auf Async zu wrappen geht anders (zB FromAsync oder AsyncDelegate).

Warum machst Du so ein riesen Code-Aufbau, wenn der WebClient bereits eine entsprechende Methode hat; und zwar DownloadStringAsync bzw DownloadDataAsync?

Bei den Icons kann ja nur die Verbindung nicht funktionieren.

Nö. Es kann nen Redirect geben, nen FileNotFound, AccessDenied, ein ungültiges Icon, Timeout.... etc etc etc
Du greifst hier auf ein Image zu, das direkt auf einem Webserver liegt.
Moderne Webserver und CDN Systeme konnen solche Requests ablehnen; nennt sich böse gesagt Traffic Diebstahl oder freundlicher Hotlinking. Außer die Seite erlaubt dies explizit, was ungewöhnlich wäre (sofern wir von einer großen Webseite reden und nicht Deinem eigenen Hosting).

R
74 Beiträge seit 2006
vor 7 Jahren

"Zwei Threads greifen auf Methode zu - Wie verhindere ich das?"

Wenn die Frage so gestellt wird, hilft zunächst das sicher weiter:
http://www.albahari.com/threading/

Später kannst Du an Deinem Problem gezielter weiterarbeiten.