JsRT; Betik Sonlandırma

   Önceki makalelerimde JsRT (JavaScript RunTime) ile tanışmış ve kendi geliştirdiğimiz bir uygulamada nasıl barındırabileceğimizi görmüştük. Bu makalemde JsRT kullanarak geliştirdiğiniz bir uygulamayı sahaya sunduğunuzda karşınıza gelebilecek önemli bir problemi ve çözümünü paylaşacağım.

   Önceki makalemde örneklediğim basit betikte hatırlarsanız, ekrana Merhaba Dünya yazarak uygulamamızı sonlandırmıştık. Sizde kabul edersiniz ki gerçek hayatta bundan çok daha karışık betikler karşımıza çıkacaktır. Hele ki bu betikler bizim tarafımızdan değil de son kullanıcı tarafından yazılıyorsa daha da karışık olabilir. Kimi iş mantıklarında, uygulamamızın sistem kaynaklarını düzgün kullanabilmesi için x dakika sonra ya da kullanıcının bir tuş kombinasyonu ile tetiklemesi ardından betiğimizin sonlanması istenebilir.

   Böylesi bir senaryoda, runtime içerisinde çalışmakta olan betiğimizi durdurabilmemizin tek yolu JsRT tarafından bize sunulan ve parametre olarak betiğin çalıştığı runtime’ı kabul eden JsDisableRuntimeExecution fonksiyonudur. Bu fonksiyonun çalışma mantığı aslında olabildiğine basittir; bir durum değişkeninin değerini ayarlar diye düşünebilirsiniz. Asıl iş ise Runtime tarafından çoktan yapılmıştır bile… Runtime, yazdığımız betiği alıp çalıştırabilir kodlara derlerken araya iptal durumunu kontrol eden kodları enjekte eder. Bu sayede betiğimizin normal işleyişi sırasında iptal durumu değiştiğinde bir sonraki kontrol noktasında fark edilecek ve çalışması sonlandırılacaktır. Mantıklı, değil mi!

   Tabi ki kaçınılmaz olarak, Runtime tarafından derleme sırasında kodumuza böylesi eklemeler yapılmasının bir performans maliyeti olacaktır. Runtime tasarımında bu durum göz önüne alınarak performans maliyetinin minimuma indirilebilmesi için gerekli önlemler alınmış durumda. Bu önlemlerden en basiti şüphesiz ki her kod satırının ardına böylesi bir kontrol eklememek 😉 Bir başkası ise döngülerde bu kontrolü gerçekleştirmemek. Bu sayede basit döngülerde katlanarak giden bir performans maliyetinin önüne geçilmiş olur. Tabi böyle bir kararın dezavantajıda olmakta; basit döngüler içerisinde betiğimiz sonlandırılamamakta, uygulamamız askıda kalarak hiç bir zaman sona eremeyebilir.

   Bir önceki paragrafta bahsettiğim durumun önüne geçebilmekte tabi ki mümkün, bunun için Runtime ilklendirilirken geçeceğimiz JsRuntimeAttributeAllowScriptInterrupt  bayrağı ile döngü sonlarında da bu kontrolün yapılmasını sağlayabiliriz. Bu değişikliğin betiğin çalışma performansını az da olsa olumsuz etkileyeceğini söylememe gerek yok sanırım.

   Konuyu pekiştirmek adına; bir önceki makalede paylaştığım kodlardan devam ederek, aşağıdaki betiği çalıştırmayı deneyelim;

var i = 0; 
for (i = 0; i>-1; i++) { 
    alert("Döngü : " + i); 
}

  Sonsuz bir döngü içerisinde ekrana o anki döngü değeri yazılacaktır;

Yazdığımız basit bir betik ile sonsuz bir döngü oluşturduk

 

   Eğer bir önceki makalemde paylaştığım örnek MSDN kodlarını kullanıyorsanız betiği sonlandırmak için doğrudan JsDisableRuntimeExecution çağrısı yapmanıza gerek olmadığını farketmişsinizdir. Bu iş için JavaScriptRuntime sınıfı içerisinde yer alan Disabled özelliğini kullanabilirsiniz. Bu özellikle kendi içerisinde gerekli fonksiyon çağrılarını yapmakta.

   Önceki makalemdeki kodumuzu test amacıyla aşağıdaki şekilde asenkrona dönüştürdüm;

using Enterprisecoding.JSHosting.Hosting; 
using System; 
using System.IO; 
using System.Threading.Tasks;

namespace Enterprisecoding.JSHosting { 
    class Program { 
        private static JavaScriptSourceContext currentSourceContext = JavaScriptSourceContext.FromIntPtr(IntPtr.Zero); 
        private static JavaScriptRuntime calismaZamani; 
        static void Main(string[] args) { 
            if (args == null || args.Length == 0) { 
                Console.Error.WriteLine("Kullanım :  JSHosting <betik adı> <parametreler>");

                return; 
            }

            Task<JavaScriptValue?> scriptTask = Task.Factory.StartNew(() => { 
                return BetigiCalistir(args); 
            });

            if (scriptTask.IsCompleted) { 
                var sonuc = scriptTask.Result; 
            } 
            else { 
                scriptTask.Wait(300);

                calismaZamani.Disabled = true; 
                var sonuc = scriptTask.Result; 
            }

            Console.ReadKey(); 
        }

        private static JavaScriptValue? BetigiCalistir(string[] args) { 
            using (calismaZamani = JavaScriptRuntime.Create()) { 
                var context = calismaZamani.CreateContext();

                using (new JavaScriptContext.Scope(context)) { 
                    var globalObject = JavaScriptValue.GlobalObject;

                    var propertyId = JavaScriptPropertyId.FromString("alert"); 
                    var function = JavaScriptValue.CreateFunction(Alert); 
                    globalObject.SetProperty(propertyId, function, true);

                    var betikAdi = args[0]; 
                    var betik = File.ReadAllText(betikAdi);

                    JavaScriptValue? sonuc = null; 
                    try { 
                        sonuc = JavaScriptContext.RunScript(betik, currentSourceContext++, betikAdi); 
                    } 
                    catch (JavaScriptScriptException e) { 
                        if (e.ErrorCode == JavaScriptErrorCode.ScriptTerminated) { 
                            Console.Error.WriteLine("JSHosting: script sonlandırıldı"); 
                        } 
                        else { 
                            var messageName = JavaScriptPropertyId.FromString("message"); 
                            var messageValue = e.Error.GetProperty(messageName); 
                            var message = messageValue.ToString();

                            Console.Error.WriteLine("JSHosting: Hata: {0}", message); 
                        } 
                    } 
                    catch (Exception e) { 
                        Console.Error.WriteLine("JSHosting: Betik çalıştırılırken hata oluştu: {0}", e.Message); 
                    }

                    return sonuc; 
                } 
            } 
        }

        private static JavaScriptValue Alert(JavaScriptValue callee, bool isConstructCall, JavaScriptValue[] arguments, ushort argumentCount) { 
            for (uint index = 1; index < argumentCount; index++) { 
                if (index > 1) { 
                    Console.Write(" "); 
                }

                Console.Write(arguments[index].ConvertToString().ToString()); 
            }

            Console.WriteLine();

            return JavaScriptValue.Invalid; 
        } 
    } 
}

Yukarıdaki kod parçacığında betiğimizi başlattıktan sonra bitip bitmediğini kontrol ederek bitmemesi durumunda tamamlanması için 300 milisaniyelik bir süre veriyorum. Dikkat ederseniz henüz JsRuntimeAttributeAllowScriptInterrupt  tanımlamasını yapmadık. Bu kod parçacığını sonsuz döngü ile çalıştırdığımızda betiğin çalışmasının sonlandırılamadığına dair “Cannot disable execution.” hatasını alacağız;

Betiğimizi çalıştırdığımızda "Cannot disable execution" hatası alacağız

   Bu hata mesajını almamak için çalışma zamanını oluşturduğumuz kodu aşağıdaki şekilde güncellemeliyiz;

calismaZamani = JavaScriptRuntime.Create(JavaScriptRuntimeAttributes.AllowScriptInterrupt, JavaScriptRuntimeVersion.Version11)

  Dikkat ederseniz Runtime’ı ilklendirirken JsRuntimeAttributeAllowScriptInterrupt belirtmek için JavaScriptRuntimeAttributes.AllowScriptInterrupt kullandık. Ardından kodumuzu çalıştırdığımızda başarılı sonucu görebiliriz;

Betiğimiz başarılı şekilde sonlandırıldı

   Koda dikkat ettiyseniz, aslında betik başarıyla sonlandırıldığında da bir hata fırlatılmakta. kodumuz içerisinde bu hata türünü yakalayıp kullanıcıyı uygun şekilde bilgilendiriyoruz.

   Toparladığımızda kodumuzun son hali aşağıdaki şekilde olacaktır;

using Enterprisecoding.JSHosting.Hosting; 
using System; 
using System.IO; 
using System.Threading.Tasks;

namespace Enterprisecoding.JSHosting { 
    class Program { 
        private static JavaScriptSourceContext currentSourceContext = JavaScriptSourceContext.FromIntPtr(IntPtr.Zero); 
        private static JavaScriptRuntime calismaZamani; 
        static void Main(string[] args) { 
            if (args == null || args.Length == 0) { 
                Console.Error.WriteLine("Kullanım :  JSHosting <betik adı> <parametreler>");

                return; 
            }

            Task<JavaScriptValue?> scriptTask = Task.Factory.StartNew(() => { 
                return BetigiCalistir(args); 
            });

            if (scriptTask.IsCompleted) { 
                var sonuc = scriptTask.Result; 
            } 
            else { 
                scriptTask.Wait(300);

                calismaZamani.Disabled = true; 
                var sonuc = scriptTask.Result; 
            }

            Console.ReadKey(); 
        }

        private static JavaScriptValue? BetigiCalistir(string[] args) { 
            using (calismaZamani = JavaScriptRuntime.Create(JavaScriptRuntimeAttributes.AllowScriptInterrupt, JavaScriptRuntimeVersion.Version11)) { 
                var context = calismaZamani.CreateContext();

                using (new JavaScriptContext.Scope(context)) { 
                    var globalObject = JavaScriptValue.GlobalObject;

                    var propertyId = JavaScriptPropertyId.FromString("alert"); 
                    var function = JavaScriptValue.CreateFunction(Alert); 
                    globalObject.SetProperty(propertyId, function, true);

                    var betikAdi = args[0]; 
                    var betik = File.ReadAllText(betikAdi);

                    JavaScriptValue? sonuc = null; 
                    try { 
                        sonuc = JavaScriptContext.RunScript(betik, currentSourceContext++, betikAdi); 
                    } 
                    catch (JavaScriptScriptException e) { 
                        if (e.ErrorCode == JavaScriptErrorCode.ScriptTerminated) { 
                            Console.Error.WriteLine("JSHosting: script sonlandırıldı"); 
                        } 
                        else { 
                            var messageName = JavaScriptPropertyId.FromString("message"); 
                            var messageValue = e.Error.GetProperty(messageName); 
                            var message = messageValue.ToString();

                            Console.Error.WriteLine("JSHosting: Hata: {0}", message); 
                        } 
                    } 
                    catch (Exception e) { 
                        Console.Error.WriteLine("JSHosting: Betik çalıştırılırken hata oluştu: {0}", e.Message); 
                    }

                    return sonuc; 
                } 
            } 
        }

        private static JavaScriptValue Alert(JavaScriptValue callee, bool isConstructCall, JavaScriptValue[] arguments, ushort argumentCount) { 
            for (uint index = 1; index < argumentCount; index++) { 
                if (index > 1) { 
                    Console.Write(" "); 
                }

                Console.Write(arguments[index].ConvertToString().ToString()); 
            }

            Console.WriteLine();

            return JavaScriptValue.Invalid; 
        } 
    } 
}

Fatih Boy

Ankara'da yaşayan Fatih, bir kamu kurumunda danışman olarak çalışmaktadır. ALM süreçleri, kurumsal veri yolu sistemleri, kurumsal altyapı ve yazılım geliştirme konularında destek vermektedir. Boş zamanlarında açık kaynak kodlu projeler geliştirmeyi ve bilgisini yazdığı makalelerle paylaşmayı seven Fatih, aynı zamanda Visual C# ve Visual Studio teknolojileri konusundan Microsoft tarafından altı yıl üst üste MVP (En Değerli Profesyonel) ödülüne layık görülmüştür. İş hayatı boyunca masaüstü uygulamaları, web teknolojileri, akıllı istemciler gibi konularda Asp.Net, Php, C#, Java programlama dilleri ve MySql, MsSql ve Oracle gibi veritabanı yönetim yazılımları ile çalışmıştır. İngilizce ve Türkçe olarak yayınlanan makalelerini gerek İngilizce bloğunda, gerekse de Türkçe bloğunda bulabileceğiniz gibi web sitesinden de açık kaynak kodlu geliştirdiği yazılımlarına ulaşabilirsiniz. vCard - Twitter - Facebook - Google+

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir