NullReferenceException על מתודה שמחזירה Task

Sea Bass

New member
NullReferenceException על מתודה שמחזירה Task

שלום לכולם.
יש לי בעיה שאני משתגע ולא מצליח להבין מאיפה זה מגיע.
יש לי מתודה שמחזירה Task ועליה אני עושה ContinueWtih.
זה נראה משהו בסגנון:
קוד:
    public async Task UpdateBlaBlaAsync(IEnumerable<SomeClass> list)
        {
            await Task.Run(() =>
            {
                if (list == null || !list.Any()) 
                    return;
                DoSomething...
            });
        }


מצד שני יש לי Instance ל Class של מתודת ה Task וזה הקוד שמשתמש בו:
קוד:
         Task updatedTask = null;
          if (list.Any())
            {
                updatedTask = myClass.UpdateBlaBlaAsync(list);
                updatedTask.ContinueWith((t) =>
                    {
                        //logg it or something
                    }, TaskContinuationOptions.OnlyOnFaulted);
            }
        }

מדי פעם, נניח פעמים שלוש ביום (וזה נקרא אלפי פעמים) נזרק לי NullReferenceException על השורה של ContinueWith.
אני לא מבין למה זה קורה. ניסיתי לחפש ולבדוק ואני ממש אובד עצות.
אשמח לעזרה.
תודה.
 

Royi Namir

New member
מממ

UpdateBlaBlaAsync לא מחזיר NULL ,זה בטוח
היא גם לא יכולה , היא מחזירה TASK שבעצם מחזיר כלום
זה שזה נופל על השורה של CONTINUE , לא אומר ש UPDATETASK נוא NULL
&nbsp
זה גם יכול להגיד שהקוד בפנים יוצר שגיאה
&nbsp
תעלה עוד קוד
האם יש עוד THREADS שנוגעים בליסט ?
&nbsp
 

Sea Bass

New member
נראה לי מצאתי... זה יכול להיות?

אין עוד טרד שפונה לליסט.
כשאני חושב על זה...
בתוך ה ContinueWith אני רושם לוג.
אני משתמש ב Log4Net. יכול להיות מצב שיש גישה לאותו Instance גם מה ContinueWith וגם מטרד אחר.
אפשר שזמן שנזרק Exception ה ContinueWith מנסה לרשום ללוג ובואתו זמן טרד אחר גם פונה לרשום ללוג.
אני אעשה Factory ל ILog.
זה הקוד שיש:
קוד:
   Task updatedTask = null;
          if (list.Any())
            {
                updatedTask = myClass.UpdateBlaBlaAsync(list);
                updatedTask.ContinueWith((t) =>
                    {
                         _log.Error("Error while update.", t.Exception);
                    }, TaskContinuationOptions.OnlyOnFaulted);
            }
        }
 

nocgod

New member
למה continueWith ולא await?

וגם שים לב שבמקביל אף אחד לא מאפס לך את _log או את list
כלומר תעשה לעצמך קוד שבודק לפני את שתיהם בסביבה מקומית.

אני נוטה גם לגשת ל loggers עם אופרטור .? כדי למנוע סיכוי שהוא null ובגלל זה התוכנה שלי תקרוס
 

Sea Bass

New member
כי await אני יכול להשתמש

רק שהמתודה שלי היא Async. במקרה הזה המתודה שקוראת ל UpdateBlaBlaAsync היא סינכרונית, הכוונה היא void Foo()
לכן כשאני מקבל Task אני משתמש ContinueWtih.
אלא אם אני אלמד תראה לי משהו אחר.. שאני לא עושה משהו טוב...
 

nocgod

New member
מה שאני חושש ממנו במצבים כאלה

של פעולה אסינכרונית שרצה בקונטקסט סינכרוני זה החוסר וודאות בחזרה של הפעולה האסינכרונית, אף אחד לא מבטיח שמישהו מחכה לה בצד השני.
מה שקורה בפעולות אסינכרונית זה שהביצוע ממשיך ולא ממתין לסיומה הcode flow שלך ממשיך הלאה. בינתיים מה שיכול לקרות הוא שהstate שאתה מניח שהוא נכון (לצורך העיניין שעדיין יש reference ל logger) כבר לא נכון.

בכללי אני נוטה להמנע מפעולות אסינכרוניות שהן בעלות closure על מי שמריץ אותן, ולו רק כדי להמנע מהצ'אנס של האחד למיליון שכשהפעולה האסינכרונית תנסה להתבצע הclosure לא יהיה בstate שמתאים לביצוע ה continuation.

קוד:
using System;
using System.Threading;
using System.Threading.Tasks;

namespace RunAwayContext
{
    class Program
    {
        static void Main()
        {
            var w = new WorkerClass(new DummyLogger()); // some worker with some poor console logger
            var d = new DummyClass {DummyString = "test"}; // some data to work with
            w.RunSomethingForMe(d); // run the worker with the data
            d.DummyString = null; // d's state change

            new CancellationTokenSource().Token.WaitHandle.WaitOne(TimeSpan.FromSeconds(1));
            Console.WriteLine("bye bye");
        }
    }

    public class WorkerClass
    {
        readonly DummyLogger _logger;

        public WorkerClass(DummyLogger logger)
        {
            if (logger == null) throw new ArgumentNullException(nameof(logger));
            _logger = logger;
        }
        public void RunSomethingForMe(DummyClass dummy)
        {
            var task = ShouldFailAtSomePoint();
            task.ContinueWith(t =>
            {
                _logger.Log(dummy.DummyString.ToLower());
            }, TaskContinuationOptions.OnlyOnFaulted);
        }

        private async Task ShouldFailAtSomePoint()
        {
            await Task.Delay(TimeSpan.FromMilliseconds(20));
            throw new Exception();
        }
    }

    public class DummyClass
    {
        public string DummyString { get; set; }
    }
    public class DummyLogger
    {
        public void Log(string s) => Console.WriteLine(s);
    }
}
 

Sea Bass

New member
אתה צודק. אני מודע לזה

ומגן מפני זה איפה שצריך.
והבעיה נפתרה. זה היה פניה ל Class שהוא לא Thread-Safe
 
למעלה