Страницы

Поиск по вопросам

среда, 27 февраля 2019 г.

Dispatcher не запускает метод в нужном потоке

У меня есть WPF окно, по событию которого вызывается комманда ViewModel. Она в свою очередь вызывает метод из BAL, запускающий дочерний поток(может жить от 10 сек до бесконечности). Данный поток генерирует события по мере выполнения и еще одно событие в самом конце, извещающее о конце работы. Тк события происходят не основном потоке, то ViewModel использует Dispatcher для синхронизации. Если я что-то на проектировал не так, буду рад узнать. Для выполнения кода в нужном потоке я использую следующий код
Dispatcher.CurrentDispatcher.BeginInvoke(new Action(SomeMethod));
Но этот код не вызывает мне нужный метод в обще. Далее я попробовал вызвать следующий код
Dispatcher.CurrentDispatcher.Invoke(new Action(SomeMethod));
В итоге я все равно получил ошибку, тк выполнение не в главном потоке. Тестирования ради я объявил поле _dispatcher и инициализировал его в конструкторе в время работы главного потока. Данный диспетчер мне уже все спокойно вызывал. С чем это связано и как мне быть? Если я вдруг создам ViewModel не в главном потоке, то у меня опять работать ничего не будет.


Ответ

Смотрите.
Dispatcher.CurrentDispatcher возвращает вам диспетчер для данного потока. Поэтому у вас нету альтернативы хранению ссылки на диспетчер.
Затем, VM-объекты обязаны бежать в UI-потоке (иначе вы не сможете биндить к ним View), так что они должны конструироваться тоже в UI-потоке. А значит, вы вполне можете рассчитывать на то, что уж в конструкторе у вас будет правильный Dispatcher.CurrentDispatcher

Почему не работает диспетчер, созданный в фоновом потоке? Диспетчер — это абстракция над циклом сообщений. Если в данном потоке бежит цикл сообщений, то Dispatcher.CurrentDispatcher его вам и возвращает. Если же для данного потока диспетчера нету, создаётся новый и прикрепляется к данному потоку. Но этот диспетчер ещё не запущен! Команда лежит в очереди и дожидается. Если вы запустите диспетчер в фоновом потоке, то все команды, отосланные ему, будут выполнены.
Однако, не пытайтесь сделать это в потоке из пула потоков: для работы диспетчера поток должен быть STA, а потоки из пула — не STA (и это нельзя поменять).
Ну и по-хорошему обычно вам должно хватать диспетчера в UI-потоке.

node.js: зачем использовать express?

Прочитал массу статей, но так и не понял какой смысл привязываться к express и шаблонизаторам? Не дает ли больше контроля над собственным приложением подход с использованием базовых модулей node.js как описано в этой книжке
Прежде всего интересует с точки зрения безопасности (в том числе "затруднение" ддос атаки), а не удобства и времени разработки.


Ответ

Шаблонизаторы удобны тем, что разметка не пишется в коде. ПОмимо отделения логики от разметки, отпаданет необходимость экранирования всяких символов и кучи конкатенаций строк.
Что касается контроля. Node.js даёт вообще максимальный контроль за происходящем. Но, чем более низкоуровневые инструменты ты используешь, тем больше придётся реализовать самому. Да, это даст максимальную гибкость в тех местах, которые нужны лично тебе. Но одновременно это значительно увеличит время разработки.
Далее, в плане безопасности. Вот стал ты делать что-то сам. Да, возможно, ты суперкрутой специалист в этой теме, сделаешь всё идеально и оно будет очень хорошо работать. Да, возможно. Но таких случаев единицы. Весьма вероятно, что ты что-то не учтёшь, сделаешь посредственно и, даже если удастся избежать явных косяков, можно просто насажать всяких дыр.
Плюс популярных модулей в том, что они широко используются и можно рассчитывать, что раз их выбрали, то они достаточно качественные, чтобы этого заслужить. Кроме того, можно рассчитывать на исправление ошибок в них, если такие обнаружатся. Минус же в том, что если вдруг выясняется какая-либо дыра, то все, кто использует этот модуль становятся уязвимы. Вполне возможно, что никого твой сайтик особо и не интересовал, чтобы искать в нём дыры, даже если их там много. Но вот опробовать нечто уже готовое на нём - почему бы нет.
В большинстве случаев быстрота разработки и достаточная надёжность перевешивают необходимость в излишней гибкости. Ну мало кто хочет себе признаться "я делаю хреновый сайт, на котором будет полтора человека и пофиг на все дыры, как-нибудь сам слеплю".
Хотя в целях обучения можно было бы попробовать написать нужную функциональность самому вместо использования готового модуля.

Проверка сервиса на thread-safe

Подскажите, пожалуйста, является ли thread-safe следующая реализация сервиса.
public class TaskReport {
private final String taskID; private final Date startTime = new Date(); private Date endTime = null; private final List report = new ArrayList(); private volatile boolean completed = false;
public TaskReport(String taskID) { this.taskID = taskID; }
public String getTaskID() { return taskID; }
public Date getStartTime() { return startTime; }
public Date getEndTime() { synchronized ( endTime ) { return endTime; } }
public void append(String entry) { synchronized ( report ) { report.add(entry); } }
public List getReport() { synchronized ( report ) { return report; } }
public boolean isCompleted() { return completed; }
public void setCompleted() { this.completed = true; synchronized ( endTime ) { this.endTime = new Date(); } }
}
public class TaskReportService {
private final ConcurrentHashMap tasks = new ConcurrentHashMap<>();
public TaskReport get(String key) { return tasks.get(key); }
public void create(TaskReport report) { tasks.putIfAbsent(report.getTaskID(), report); }
public void delete(String key) { tasks.remove(key); }
}
Насколько я понимаю ConcurrentHashMap обеспечивает потокобезопасность только атомарных операций, т.е. еще необходима дополнительная синхронизация содержимого. А не может ли в такой ситуации возникнуть дедлока?


Ответ

В этом коде много проблем с потокобезопасностью, хотя многие вещи зависят от того, как он используется. Например, TaskReportService.create
public void create(TaskReport report) { tasks.putIfAbsent(report.getTaskID(), report); }
Вас точно устраивает ситуация, что созданный снаружи кем-то TaskReport может быть помещён в map, а может и не быть? Причём вы никак не узнаете, был ли он помещён. Я могу снаружи сделать:
TaskReport report = new TaskReport("foo"); service.create(report); report.append("bar");
При этом совершенно неизвестно, сохранилась ли строчка "bar" или нет. Допустим вы всегда вызываете service.create для свежесозданного TaskReport, а потом его не используете. Зачем тогда его создание выносить наружу, раз это может привести к потенциальным проблемам? Не проще ли сделать метод getOrCreate?
public TaskReport getOrCreate(String key) { TaskReport report = tasks.get(key); if(report != null) return report; // Нету - создаём TaskReport newReport = new TaskReport(key); TaskReport oldReport = tasks.putIfAbsent(key, newReport); if(oldReport != null) { // Ой, кто-то в другом потоке только что создал с тем же ключом вперёд нас // Выкидываем наш новый и возвращаем тот, что есть return oldReport; } // Возвращаем свежесозданный return newReport; }
Теперь если вы будете для создания TaskReport пользоваться только этим методом, у вас гарантировано не будет двух репортов для одного ключа, по крайней мере пока вы не начнёте удалять и создавать с тем же ключом.
Кстати, если у вас Java-8, вышеприведённый метод будет однострочным:
public TaskReport getOrCreate(String key) { return tasks.computeIfAbsent(key, TaskReport::new); }
Далее — класс TaskReport. Тут тоже многое зависит от того, как он используется. Попытки синхронизации подсказывают, что один и тот же TaskReport может использоваться из нескольких потоков одновременно. Вот такой код абсолютно бесполезен:
public Date getEndTime() { synchronized ( endTime ) { return endTime; } }
Вы совершенно ни от чего не защищаетесь. А вот это ещё хуже:
synchronized ( endTime ) { this.endTime = new Date(); }
На это вроде даже FindBugs ругается. Бессмысленно синхронизоваться по объекту в поле и присваивать в это же поле другое значение. Надо сперва понять, от чего вы защищаетесь. Если вы не собираетесь менять содержимое объектов Date (например, вызывая Date.setTime()), то вам нечего защищать. В крайнем случае объявите поле endTime как volatile, чтобы изменения были видны в других потоках сразу. Синхронизация здесь излишня и бесполезна.
Аналогично бесполезно вот это:
synchronized ( report ) { return report; }
Когда вы уже вернёте ArrayList наружу, блок синхронизации завершится и дальше всё зависит от того, что будет делать внешний пользователь с этим списком. Если он собрался обходить его итератором (например, чтобы вывести строки в интерфейсе пользователя), вас не спасает ни синхронизация в append, ни в этом методе. Вы можете словить ConcurrentModificationException или чего похуже. Вас не спасёт даже оборачивачивание списка в Collections.synchronizedList. Здесь я вижу три варианта:
Если вы часто пишете и редко читаете, проще вернуть копию:
public List getReport() { synchronized ( report ) { return new ArrayList<>(report); } } Если вы редко пишете и часто читаете, можно воспользоваться CopyOnWriteArrayList
private final List report = new CopyOnWriteArrayList();
В этом случае синхронизация не нужна вообще (ни в append, ни в геттере). Просто верните report в геттере. Можно обернуть в Collections.unmodifiableList для безопасности. Если вы часто пишете и часто читаете, но вам не нужен случайный доступ, воспользуйтесь ConcurrentLinkedDeque:
private final Collection report = new ConcurrentLinkedDeque();
public Collection getReport() { return Collections.unmodifiableCollection(report); }
В этом случае тоже в append синхронизация не нужна.

Передача массива из функции

Бинарный файл составляю из 2 source-файлов, в одном основной код, в другой функции генерации ssl-сертификата. Функция формирования сертификата (дополнительные функции не привожу) из второго файла.
int create_cert(unsigned char *buf, int *len) { BIO *bio_err; X509 *x509=NULL; EVP_PKEY *pkey=NULL; unsigned char *p; int i;
CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON); bio_err=BIO_new_fp(stderr,BIO_NOCLOSE); mkcert(&x509,&pkey,2048,0,1490);
*len=i2d_X509(x509,NULL); buf=malloc(*len); if (buf==NULL) return -1; p=buf; i2d_X509(x509,&p);
X509_free(x509); EVP_PKEY_free(pkey); #ifndef OPENSSL_NO_ENGINE ENGINE_cleanup(); #endif CRYPTO_cleanup_all_ex_data(); CRYPTO_mem_leaks(bio_err); BIO_free(bio_err);
return 1; }
Тут я генерирую сертификат средствами openssl, с помощью i2d_X509 перевожу в DER-формат и записываю в память под указателем buf (а так же размер массива сертификата), который должен возвратиться туда где я вызываю эту функцию (т.е. в основной файл).
А вот главная функция первого файла.
int main(void) { char Cert; unsigned char *cbCertificate; int certLen;
create_cert(&cbCertificate,&certLen); }
Тут я принимаю указатель и размер массива сертификата с предыдущей функции. Все никак не могу понять как можно взять массив из памяти и присвоить его в переменную Cert, чтобы после передать другой функции. Я попытался записать сертификат в файл с помощью
FILE *fp=fopen("cert.cer","w"); if (fp!=NULL) { fwrite(&cbCertificate,1,certLen,fp); fclose(fp); }
Записывается всякая хрень. Я понимаю что cbCertificate это указатель на указатель. Я понимаю как можно взять адрес указателя, но вот к данным получить доступ не понимаю как.


Ответ

Если я правильно Вас поняла, и Вы хотите вернуть из внутренней функции наружу выделенный внутри нее массив, используя в качестве параметра этой функции двойной указатель, то, для начала, нужно описать в параметрах этой функции двойной указатель
int create_cert(unsigned char **buf, int *len){...}
Далее, выделять память под массив Вам нужно с учетом, что вы используете двойной указатель (Вы аналогично уже работаете с len):
*buf = (unsigned char *)malloc(*len);
Тогда, после вывова функции create_cert() в cbCertificate будет лежать указатель на новый, выделенный внутри create_cert() массив.
Все никак не могу понять как можно взять массив из памяти и присвоить его в переменную Cert
Так, как у Вас описано, это точно не получится, так как переменная Cert у вас имеет тип char, - т.е. 1 элемент. Нельзя одному элементу типа char присвоить весь массив.
Если же Вы, после того, как получили с помощью функции create_cert() данные, хотите записать их в файл, то запись будет выглядеть вот так :
fwrite(cbCertificate,1,certLen,fp);
так как
unsigned char *cbCertificate;
это не двойной, а обычный указатель. Вот когда вы берете его адрес, как здесь :
create_cert(&cbCertificate,&certLen);
внутри функции create_cert() получается двойной указатель.
UPD: Как выглядят двойные указатели:
Объявление :
int **cbCertificate;
Тогда инициализация такого указателя может выглядеть вот так :
int a = 0; int *ptr = &a; int **cbCertificate = &ptr;
И передавать такой двойной указатель в функцию можно будет вот так :
void foo(int ** pptr){...} ... int main(){ int a = 0; int *ptr = &a; int **cbCertificate = &ptr;
foo(cbCertificate); }
Если же двойной указатель нам нужен только для того, чтобы вернуть из функции адрес выделенного внутри этой функции буффера, то можно обойтись использованием двойного указателя только на стеке функции, тогда вышеприведенный пример можно записать так :
void foo(int ** pptr){...} ... int main(){ int a = 0; int *ptr = &a;
foo(&ptr); }
Двойной указатель является не чем иным, как адресом некоторого указателя, следовательно, работать с самим указателем и данными внутри функции foo() можно следующим образом:
void foo(int **pptr){ ... *pptr = NULL; // инициализация указателя, адрес котрого хранится в pptr //(pptr все еще хранит значение, указывающее на некоторую ячейку памяти, //а вот эта самая память уже никуда не указывает) *pptr = (int *)malloc(10*sizeof(int)); // ячейка, на которую указывает // pptr, указывает на массив из 10 значений типа int
(*pptr)[0] = 5; // инициализируем первый элемент выделенного массива ... }

Обработка ошибок в Rust

Есть программа на rust, взятая отсюда: Учебник по Rust
Захотелось мне, чтобы программа постоянно выводила собственный префикс ("guess_game>> "). По-этому немного пришлось подкорректировать строки перед запросом ввода.
В оригинале было так
println!("Пожалуйста, введите предположение.");
Если просто заменить println! на print!, у меня вывод сбрасывался на терминал слишком поздно. Выход был найден в принудительном вызове stdout().flush(). Но, когда я добавил обработку ошибок, как приведена ниже, при запуске программы стали бесконечно рисоваться строки "guess_game>> try: ". Прошу объяснить, почему так и какова логика?
extern crate rand;
use std::io::stdin; use std::io::Write; use std::io::stdout; use rand::Rng; use std::cmp::Ordering;
fn main() { let snum = rand::thread_rng().gen_range(1, 101); println!("guess_game>> rnd: {}", snum);
loop { let mut guess = String::new();
print!("guess_game>> try: "); match stdout().flush() { Ok(_) => continue, Err(_) => continue, };
stdin().read_line(&mut guess) .ok() .expect("Fail read line");
let guess: u32 = match guess.trim().parse() { Ok(num) => num, Err(_) => continue, };
match guess.cmp(&snum) { Ordering::Less => println!("guess_game>> Less"), Ordering::Equal => { println!("guess_game>> Equal"); break; } Ordering::Greater => println!("guess_game>> Greater"), } } }
UPD: Решение:
print!("guess_game>> try: "); match stdout().flush() { Ok(_) => (), Err(_) => (), };


Ответ

У вас бесконечно выводится guess_game>> try: по тому, что у вас получился бесконечный цикл.

Форматирование вывода данных в консоли


Как красиво вывести числа в несколько столбцов, чтобы они не скакали?
Код:
for (int i = 0; i < 1000; i++) { if (i % 3 == 0 || i % 5 == 0) { Console.Write(i + " | "); sum += i; } }


Ответ

Попробуйте следующий код:
int sum = 0; int count = 0; for (int i = 0; i < 1000; i++) { if (i % 3 == 0 || i % 5 == 0) { Console.Write("{0, 7} | ", i); sum += i; count++; if (count == 8) { count = 0; Console.WriteLine(); } } }
Обратите внимание на следующее:
Для выравнивания выдачи чисел используется строка формата {0, 7}. Подробнее: MSDN Для сохранения столбцов от строки к строке, после каждых 8 чисел выдается перевод строки: Console.WriteLine()

Вызов функции из другого файла

Есть общий файл (main.js) для всех скриптов, в нём прописана функция для кнопки "наверх":
function button_top_right (n) { var top_button = $("#main").offset().top; $("#top_right").hide(); $(window).scroll(function () { if ($(this).scrollTop() > top_button + n) { $('#top_right').fadeIn().addClass("fixed"); } else { $('#top_right').fadeOut(); } }); }; button_top_right (1500);
Для одной страницы мне надо вызвать эту функцию с другим параметром n Добавляю скрипт на эту страницу:
$(document).ready(function () { button_top_right (100); });
Скрипт не срабатывает с ошибкой:
Uncaught ReferenceError: button_top_right is not defined
main.js подгружается раньше, чем вызов функции. Что я делаю не так?


Ответ

Когда функция объявлена внутри document.ready, обратиться к ней извне не получится. Достаточно вынести саму функцию, а её вызовы оставить внутри document.ready
function button_top_right(n) { ... }; $(document).ready(function() { button_top_right(1500); });
Так как внутри функции button_top_right при каждом вызове функции добавляется обработчик события scroll для window, имеет смысл убирать предыдущий обработчик либо перед вызовом функции, либо внутри функции перед добавлением нового:
$(window).off("scroll").on("scroll", function() { ... });