Оптимизация вызовов функций из воркеров (web-workers)

от автора

Приветствую уважаемое Хабросообщество, и в качестве взноса в этот банк коллективного разума
— хочу поделиться своим опытом в работе с воркерами.

Воркеры (Web-workers), это технология, позволяющая запускать изолированные участки кода в отдельном потоке. Код из воркера не тормозит работу графического интерфейса, и выполняется быстрее, чем код на странице, что делает использование воркеров очень привлекательным, для ресурсоёмких расчётов, типа рисования графики или криптографии.

Кто ещё не встречался с этой технологией — здесь можно ознакомится с её основами.

Ниже, описан небольшой «лайфхак», который позволяет уменьшить количество кода, необходимого для вызова функций из воркера, если нужно вызывать больше одной функции.

Обычно, если в нашем воркере всего одна функция, то мы просто пишем:

снаружи(в коде страницы):

var worker = new Worker("myscript.js");   worker.onmessage (event.data){workerСallback(event.data);} function workerСallback(data){ /*do something with data object;*/} worker.postMessage({some:"some"}); 

внутри(в коде воркера):

onmessage = function (event) {postMessage(mySingleFunction(event.data));} 

Пока, всё просто и изящно.

Однако, если в воркер добавить ещё одну функцию, вызываемую извне, то количество кода, обеспечивающего вызов этих функций — возрастёт, и он будет выглядеть уже не так изящно:

снаружи:

function firstFunctionСallback(data){   /*do something with data object;*/}  function secondFunctionСallback(data){ }  worker.onmessage (msg){   if(msg.data.callback == "firstFunctionСallback"){       firstFunctionСallback(msg.data.result);   }   if(msg.data.callback == "secondFunctionСallback"){       firstFunctionСallback(msg.data.result);   }  }  worker.postMessage({functionName: "firstFunction", data: data); 

внутри:

onmessage = function (event) {   var functionName = event.data.functionName;   if(functionName == "firstFinction"){       postMessage({callback: "firstFunctionСallback", result: firstFinction(event.data.data)});   }   if(functionName == "secondFunction"){       postMessage({callback: "secondFunctionСallback", result: secondFunction(event.data.data)});   }   ... } 

При таком подходе, нет возможности использовать в качестве коллбэков анонимные функции, и при каждом добавлении новой функции в воркер, нужно каждый раз, снаружи и внутри воркера, писать некоторое количество дополнительного кода, обеспечивающего вызов этой функции.

Чтобы избежать этого — воркер можно обернуть в объект, который будет выполнять эту работу.

Назовём такой объект, соответственно, Performer, и разместим его во внешнем коде:

function Performer(scriptSource) { 	var worker = new Worker(scriptSource), callbacks = {}, nextRequestId = 0;	 	this.perform = function(functionName, params, callback) { 		callbacks["request_" + (++nextRequestId)] = callback; 		worker.postMessage( 		 {functionName: functionName, params: params, requestId: nextRequestId} 		); 	}	 	worker.onmessage = function(msg) { 		callbacks["request_" + msg.data.requestId](msg.data.result); 		delete callbacks["request_" + msg.data.requestId]; 	} } 

Во внутреннем коде воркера — изменим обработчик внешних сообщений:

onmessage = function (event) {         var requestId = event.data.requestId;   var workerFunction = eval(event.data.functionName);   var params = event.data.params;   var result =  workerFunction(params);   postMessage({result: result, requestId: requestId});  } 

Теперь, можно добавлять в воркер любые функции, и вызывать их извне, без написания вспомогательного кода, а также, использовать анонимные функции в коллбэках:

var performer = new Performer("myscript.js"); performer.perform("firstFunction", {some: "some"}, function(result){console.log("result1="+result);});	 performer.perform("secondFunction", {some: "some"}, function(result){console.log("result2="+result);});	  

Ели воркер размещён не в отдельном файле скрипта, а встроен в страницу,
то в коде перформера должны быть учтены кроссбраузерные различия.

С их учётом, часть перформера, которая отвечает за инициализацию воркера, будет выглядеть так:

function Performer(scriptText) {     var worker = null;     	try {// Firefox 		var Url = window.webkitURL || window.URL; 		worker = new Worker(Url.createObjectURL(new Blob([ scriptText ]))); 	} catch (browserNotSupportWindowUrl) { 		try {// Chrome 			worker = new Worker('data:application/javascript,' + encodeURIComponent(scriptText)); 		} catch (browserNotSupportWorkers) {// Opera					 			eval(scriptText); 			worker = { 				postMessage : function(data) { 					var workerFunction = eval(data.functionName);	 					worker.onmessage({ 						data : { 							result : workerFunction(data.params), 							requestId : data.requestId 						} 					}); 				} 			}; 		} 	} ... } 

а создание, соответственно, так:

var performer = new Performer($('#myscript').text()); 

Таким образом, даже в браузерах не поддерживающих воркеры, код воркера всё равно будет выполняться, просто медленнее.

UPD: благодарю Неизвестного Благодетеля за инвайт).

ссылка на оригинал статьи http://habrahabr.ru/post/207890/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *