Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "WinAPI";
Текущий архив: 2006.10.22;
Скачать: [xml.tar.bz2];

Вниз

ошибка в уничтожении потока Delphi+Thread+TerminateThread   Найти похожие ветки 

 
Delphi5.01 ©   (2006-05-31 06:13) [0]

Хотя в конкретном моем случае речь идет о Delphi, но думаю вопрос больше общий так как касается API функции, которые одинаковые как по синтаксису так и среде использованию для всех (почти) языков. Дело касается потоков и функции TerminateThread(thread.Handle, 0);

Теперь немного о моей программе. В программе есть потоки которые выполняют некую работу. Допустим качают данные с какого-то сайта. Назовем его "поток скачки". Так как у скачки должен быть timeout, я "поток скачки" поместил в другой поток который сделал за временем, и в случае его истечения заверещал "поток скачки" аварийно, TerminateThread("поток скачки".handle, 0); Назовем этот "поток проверки".

В итоге что бы выполнить задания закачки одновременно я создавал несколько "потоков проверки" и ждал пока они все закончат работу. Результат мог быть или положительный, а значит "поток скачки" завершил работу удачно, либо отрицательный, не смогли скачать или кончилось время. После чего данный "поток проверки" убивался также функцией TerminateThread("поток проверки".handle, 0); после чего заново запускался но уже с новыми параметрами.

Но при тестировании и наблюдениями за поведением кода, было замечено что после того как первые 10 "потоков проверки" (а это был массив) завершал работу и запускались новые 10 потоков некоторые вообще исчезали. Другими словами есть подозрение что функция TerminateThread убивала больше одного потока за раз! Так как при подсчете, а я учитывал когда поток заканчивал работу, разница была в 50%. Толи потоки не могли быть убиты, толи наоборот их убили случайно :-)

Одним словом, конечно сложно что-то советовать прочитав выше указанный алгоритм, но может у вас была похожая ситуация? И вообще как можно завершить работу потока аварийно. Что бы наглухо, так как алгоритм Thread.Teriminate не подходит, он завершает работу только после того как внутренние компоненты (созданные в потоке) завершат работу.

Заранее благодарю всех сначала за время потраченное на прочтение такого большого текста и потом за ответ :-)


 
Delphi5.01 ©   (2006-05-31 06:29) [1]

Должно быть:
...поток который следит за временем...


 
Delphi5.01 ©   (2006-05-31 08:01) [2]

Сори но кажеться я сам лично нашел ошибку, досканально проверял каждый компонент по отдельности и кажеться ошибка вообше банальная  (в чужом компоненте не доработка )


 
Сергей М. ©   (2006-05-31 08:25) [3]


> как можно завершить работу потока аварийно


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

Твой случай скорей всего принадлежит этому большинству, просто ты не видишь каких-то (неочевидных) способов сообщить потоку о необходимости немедленного его завершения.


 
DVM ©   (2006-05-31 10:06) [4]


> В подавляющем большинстве случаев можно обойтись без аварийного
> терминирования потока, т.е. практически всегда найдется
> способ штатного завершения работы поточной ф-ции.

При работе с сокетами (сетью), к сожалению, нормального способа быстро уничтожить поток нет. Если сокет ждет подключения (ответа сервера), сервер не отвечает, поток можно будет убить только после возникновения таймаута. Многие известные программы (в том числе и от MS) уничтожают потоки аварийно, а потом как-то подчищают ресурсы.


 
Сергей М. ©   (2006-05-31 10:08) [5]


> DVM ©   (31.05.06 10:06) [4]



> При работе с сокетами (сетью), к сожалению, нормального
> способа быстро уничтожить поток нет


Это заблуждение.


 
DVM ©   (2006-05-31 15:34) [6]


> Это заблуждение.

Развейте мои заблуждения конкретным примером (кодом). Прежде всего интересует код, немедленно останавливающий поток, висящий на функциях Winsock.Connect() или Winsock.recv(). Сокеты блокирующие.


 
Alex Konshin ©   (2006-05-31 15:42) [7]


> DVM ©   (31.05.06 10:06) [4]
> При работе с сокетами (сетью), к сожалению, нормального
> способа быстро уничтожить поток нет. Если сокет ждет подключения
> (ответа сервера), сервер не отвечает, поток можно будет
> убить только после возникновения таймаута. Многие известные
> программы (в том числе и от MS) уничтожают потоки аварийно,
>  а потом как-то подчищают ресурсы.


Это абсолютная ерунда. Нужно просто правильно писать приложения.
Если использовать синхронную работу с сокетами, то могут быть затруднения ( и то можно забороться), но с асинхронными никаких проблем нет, правда кода писать нужно побольше и повнимательнее.
TerminateThread нужно избегать и использовать только в крайних случаях. Например, я лично не совсем представляю, что будет, если вырубить нить, которая в какой-то критической секции, да и как-то нет желания проверять.
Слишком уж много возможностей самого себя запутать.


 
Alex Konshin ©   (2006-05-31 15:45) [8]


> DVM ©   (31.05.06 15:34) [6]
> > Это заблуждение.
> Развейте мои заблуждения конкретным примером (кодом). Прежде
> всего интересует код, немедленно останавливающий поток,
> висящий на функциях Winsock.Connect() или Winsock.recv().
>  Сокеты блокирующие.

А зачем блокирующие? Создать себе трудности, чтобы потом героически их преодолеть?
В данном случае попытайся закрыть сокет, а потом уж закрывай нить.


 
DVM ©   (2006-05-31 15:50) [9]


> Alex Konshin ©   (31.05.06 15:42) [7]


> Это абсолютная ерунда.

Да неужели? Сокеты как я уже написал синхронные. Функция, скажем, recv() не вернет управление, пока не возникнет таймаут, заданный в TTimeVal.tv_sec. И корректно до наступления этого события поток не закрыть. Либо Terminate и ждать, либо уничтожать.

> TerminateThread нужно избегать и использовать только в крайних
> случаях

А я это и не пропагандирую.

Хотелось бы все таки ответ на [6]


 
DVM ©   (2006-05-31 15:52) [10]


> В данном случае попытайся закрыть сокет

Сокет не закроется, пока recv() не вернет управление, все будет висеть как раньше.


 
DVM ©   (2006-05-31 16:00) [11]


> Создать себе трудности, чтобы потом героически их преодолеть?

Я не люблю неблокирующие сокеты. Это изобретение было специально сляпано для Win3.11 да так и прижилось. Программировать многие задачи с неблокирующими сокетами не легче, а, наоборот, труднее.


 
Eraser ©   (2006-05-31 16:10) [12]


> DVM ©

а зачем вообще немедленно уничтожать поток, который больше не нужен?
допустим при использовании наследников TThread можно просто вызвать метод Terminate, который, в свою очередь, сделает Terminated = true, и почаще проверять этот самый Terminated внутри потока, например сразу после синхронного вызова, и поступать например так:
if Terminated then
 Exit;


 
Сергей М. ©   (2006-05-31 16:11) [13]


> DVM


Блокирующий recv() немедленно вернет управление (с соотв.кодом ошибки), как только в другом (по отношению к данному) потоке будет вызвана closesocket() с указанием хэндла гнезда, "висящего" на recv()

То же самое касается любого другого блок.вызова из состава Winsock.


 
DVM ©   (2006-05-31 16:16) [14]


> а зачем вообще немедленно уничтожать поток, который больше
> не нужен?

Попробую привести пример:
Имеется компонент, получающий данные с сервера. Данные он получает в отдельном потоке и потом, скажем отображает.
Экземпляры этого компонента динамически создаются и уничтожаются в процессе работы программы. При уничтожении компонент делает потоку Terminate и ждет WaitFor завершения потока. Уничтожить себя раньше потока нельзя, так как потоку может что-то понадобиться от компонента.
Имеем: поток висит на функции и ждет таймаута, компонент и программа ждут. Все висит.


 
Alex Konshin ©   (2006-05-31 16:17) [15]

Я никогда не использую блокирующие сокеты. Так что это твои прблемы.
Почитай внимательно описание closesocket()
http://msdn.microsoft.com/library/en-us/winsock/winsock/closesocket_2.asp
Но, я все равно буду рекомендовать асинхронные сокеты, потому как там все предсказуемо.


 
Eraser ©   (2006-05-31 16:23) [16]


> DVM ©   (31.05.06 16:16) [14]

ситуция понятная и довольно часто встречающаяся, решение - изменение архитектуры, чтобы не было Уничтожить себя раньше потока нельзя, так как потоку может что-то понадобиться от компонента.
Или же перед обращением из потока к полям компонента, проверять, существует ли этот компонент. В свою очередь компонент при уничтожении должен обнулять указатель на себя в экземпляре класса "своего" потока.


 
DVM ©   (2006-05-31 16:27) [17]


> Alex Konshin ©   (31.05.06 16:17) [15]

Почитал описание на msdn.
Насчет closesocket я был вроде бы не прав. Сейчас проверю на практике.


 
Alex Konshin ©   (2006-05-31 16:36) [18]

> DVM ©   (31.05.06 16:16) [14]
> Попробую привести пример:
> Имеется компонент, получающий данные с сервера. Данные он
> получает в отдельном потоке и потом, скажем отображает.
> Экземпляры этого компонента динамически создаются и уничтожаются
> в процессе работы программы. При уничтожении компонент делает
> потоку Terminate и ждет WaitFor завершения потока. Уничтожить
> себя раньше потока нельзя, так как потоку может что-то понадобиться
> от компонента.
> Имеем: поток висит на функции и ждет таймаута, компонент
> и программа ждут. Все висит.

Остается только задать вопрос: "Кто виноват?"
А зачем ждать по WaitFor? А почему не по WaitForMultipleObjects/WSAWaitFor.../MsgWaitForMultipleObjects?
Почему не иметь событие, которое специально предназначено для вывода из ожидания? Вот и получаем очень простое и надежное решение:
Ждем нескольких событий, одно из которых признак Attention. При выходе из ожидания проверяем код возврата, а также некий флаг (если используется TThread то нужно проверять Terminated). Если от нас хотят завершения, то закрываем все сокеты и все. Все остальные нити завершатся сами собой, правда я все равно с таймаутом жду и уж тогда убиваю.
Естественно, я говорю про Winsock2, т.к. только там есть приемлемое ожидание событий сокета.


 
Delphi5.01 ©   (2006-05-31 17:27) [19]

Ну ладно если все так легко вы попробуйте считать информацию с внешнего носителя, например CD и потом убить поток! Если информация считывается с поврежденной части CD то нафиг поток не убить! И даже смысла нет давать команду suspend нужно ждать пока сгенериться ошибка I/O и только потом поток завершит работу, а таимаут в таком случае от 30 до 60 секунд, в зависимости от размера считываемой информации. Полтора года назад писал данный код с считыванием ифны, тогда даже о функции TerminateThread-не знал и приходилось до минуты ждать завершения потока.
Теперь вернусь к моему частному примеру. Я использую компонент TIdHTTP для получения информации, как уже говорили, есть вероятность что повиснет компонент именно при коннекте Как в этом случае его прибить? Может просто вызвать IdHTTP.Free? а потом самого потока?


 
umbra ©   (2006-05-31 17:46) [20]

так у TIdHTTP есть свойство ConnectTimeout. Выставьте ему приемлемое значение и ловите исключение EIdConnectTimeout


 
DVM ©   (2006-05-31 17:57) [21]


> Я использую компонент TIdHTTP для получения информации,
> как уже говорили, есть вероятность что повиснет компонент
> именно при коннекте Как в этом случае его прибить? Может
> просто вызвать IdHTTP.Free? а потом самого потока?

Исходников компонента нет, поэтому можно только гадать, что делается в IdHTTP.Free. CloseSocket() из основного потока решает проблему, как я убедился только что. Но не факт, что закрытие сокета у этого компонента извне не приведет к печальным последствиям.


 
DVM ©   (2006-05-31 18:00) [22]


> Delphi5.01 ©   (31.05.06 17:27) [19]

IdHTTP1.Socket.Close; ???


 
umbra ©   (2006-05-31 18:09) [23]

а чтобы не тормозил интерфейс при коннекте есть TIdAntifrreze


 
Delphi5.01 ©   (2006-05-31 18:18) [24]


> так у TIdHTTP есть свойство ConnectTimeout

Насколько точно данный таймаут работает верно? Есть ли исключения при которых таймаут не сработает. Например если повисание при connect-е?


> IdHTTP1.Socket.Close; ???

Думаю это то что надо. Я для страховки буду делать вот так.

буду устанавливать параметр
IdHTTP.ConnectTimeout

Потом по таймауту потока, если главный таимаут не сработал за "время + 2сек"
IdHTTP.Socket.Close;
IdHTTP.Free;
TerminateThreat();

Думаю после всего этого все пройдет супер, если возникнут траблы с прикрытием то прибьем все автоматом. Хотя меня вот что интересует возвратить ли управления процедура/функция Socket.Close даже если компонент подвивший?


 
DVM ©   (2006-05-31 18:31) [25]


> Хотя меня вот что интересует возвратить ли управления процедура/функция
> Socket.Close даже если компонент подвивший?

Если подвис на Winsock функциях, то возвратит.


 
DVM ©   (2006-05-31 18:36) [26]


> а чтобы не тормозил интерфейс при коннекте есть TIdAntifrreze

Так у него доп. поток вроде. И так не будет тормозить. Поток лучше, чем TIdAntifrreze, т.к. последний это лишь Application.ProcessMessages по таймеру.


 
Eraser ©   (2006-05-31 18:49) [27]


> DVM ©   (31.05.06 17:57) [21]


> Исходников компонента нет

как это нет? ещё как есть.


 
Delphi5.01 ©   (2006-06-01 03:14) [28]

Остается одно, делать как советуют и пробовать на большом количестве созданий и уничтожений потока, и главное в аварийной ситуации. При этом надо наблюдать за системными ресурсами.
Тестирование буду проводить сегодня, если несколько дней не увидите моего поста, значит загубил систему :-)


 
Delphi5.01 ©   (2006-06-01 05:55) [29]

Блин в D6 у копонетна IdHTTP нет Socket.Close :-( наверно вы говорите о D9? У меня однажды была похожая ситуация но с SSL и компонентом Telnet. Или я что-то не првильно понял?


 
Delphi5.01 ©   (2006-06-01 05:57) [30]

procedure DisconnectSocket; virtual;

Description

DisconnectSocket is a procedure used to close the socket binding for the connection. Unlike the Disconnect method, DisconnectSocket is not overridden by descendant classes to provide termination sequences required for a particular protocol.
DisconnectSocket is used when an error has occurred in a protocol handler and a guaranteed disconnect is needed.


 
Delphi5.01 ©   (2006-06-01 06:10) [31]

Думаю теперь все грамотно?

Thread.TerminateConnection;
Thread.Terminate;
Thread.Free;

procedure TMyThread.TerminateConnection;
begin
 fIdHTTP.DisconnectSocket;
 fIdHTTP.Free;
end;

Что скажете теперь знатоки? Проблем и ошибок не было, код работает как часики, если мне не показалось то он работает намного быстрей :-) Чем с конструкцией TerminateThread :-)


 
Error_   (2006-06-01 09:13) [32]

Как и можно было ожидать, где-то просочилась ошибка :-( Хоть поток удаляется по всем правилам, где-то генерируется ошибка и прога автоматом, без сообщений об ошибке вырубается Когда и при каких обстоятельствах пока не известно, но где-то после 200 запуска/уничтожения потока (прогу оставил работать, сам пошел чаек попить).
Сейчас добавлю в программу ведения логов и буду ловить ошибку. А как можно отловить все ошибки возникшие при работе программы? Что бы хотя бы понять где приблизительно ошибка


 
Error_   (2006-06-01 10:06) [33]

Лог файлы показали что, после 500 запусков и уничтожений остаются потоки которые не могут быть завершены! А именно мое подозрение касающийся fIdHTTP.DisconnectSocket не срабатывает и поток продолжает работать. Сейчас думаю есть смысл проверить что будет с TerminateThread и запуском 500 раз? Если ошибок не будет то мое подозрение подтвердиться фактом


 
DVM ©   (2006-06-01 10:11) [34]


> Delphi5.01 ©   (01.06.06 06:10) [31]

После Terminate потока все таки лучше ждать завершения


 
Рез_Тест_2   (2006-06-01 10:30) [35]

Тестирование с функцией TerminateThread

Начну с самого лавного, утечка в памяти есть. Не все ресурсы высвобождаются, хотя зато все работает как часы. Потеря не велика до 1мб при запуске/уничтожении 1000 потоков. Сначала было больше пока я не добавил строку IdHTTP.Free до уничтожения потока.

Теперь возникает логический вопрос, как все же убить поток удачно, или что еще надо зачистить? Кроме IdHTTP поток не использует ничего кроме переменных. Не думаю что именно они и не освобождаются И как можно системе передать сообщение что типа убей поток и подчисти все, или дядя Бил это не предусмотрел? И по этой причине винда часто летит?


 
Рез_Тест_2   (2006-06-01 10:33) [36]


> После Terminate потока все таки лучше ждать завершения

И по этой причине и подвисла программа, так как поток так и не смог приглушить сокет, и поток как говориться подвис. Такое у меня было с I/O, когда считывал с поврежденной болванки, в таком случае надо ждать пока не сработает Timeout самого сокета :-( И сработает ли вообше? В моем случае просто прога вырубалась автоматом, без всяких ошибок.
С TerminateThread такого нету, все как и запланированно, но вот с освобождением ресурсов не все OK


 
Alex Konshin ©   (2006-06-01 14:40) [37]

Ты не прав с тем, что сразу после закрытия сокета выдаешь Free на нить.
Дай ей время на нормальное завершение. Просто через какое-то время проверяй WaitForSingleObject с нулевым таймаутом (если проверяющая нить занимается чем-то еще) или жди с таймаутом (если ей делать нечего).
Кстати, у тебя чему равно FreeOnTerminate для этих нитей.
Вопрос очень важный, т.к. если он True, то объект будет освобождаться дважды (если меры не принять), а если False, то тебе нужно следить за удалением самому. Собственно это самый сложный момент во всей этой кухне и это не просто сделать действительно надежно. Наверно для тебя самое простое будет посмотреть на класс TThreadList. Суть в том, чтобы всегда добавлять/удалять нити в/из спис-ок/ка, но не уничтожать, если нити там нет. Естественно, не забывать блокировать список при операциях с ним. Это может быть несколько утомительно, но зато более надежно.

Короче, как я и говорил грамотно все сделать непросто, потому все ленятся и потому мы видим приложения, которые виснут.


 
DVM ©   (2006-06-01 16:52) [38]


> Думаю теперь все грамотно?

Лучше сделай так:

Thread.FreeOnTerminate := false;

Thread.fIdHTTP.DisconnectSocket;
Thread.Terminate;
Thread.WaitFor;
Thread.Free;
Thread := nil;

procedure TMyThread.Destroy;
begin
fIdHTTP.Free;
end;


 
Error_   (2006-06-02 03:22) [39]

Сори что долго не отвечал, был в out-е :-)
Вот код который я имею на данный момент, как можно догадаться он для проверки работаспособности прокси:

unit Kernel;

interface

uses
 Classes, IdHTTP, Windows, Functions, Dialogs;

type
 TProxyType = (ptUnknown, ptBad, ptTransparent, ptAnonymouse, ptElite, ptElitePlus);

 TProxyCheckerThread = class(TThread)
 private
   fIdHTTP: TIdHTTP;
   fUrl: String;
   fCheckString: String;
   fResult: Boolean;
   fResultString: String;
 protected
 public
   constructor Create(Url, Server: String; Port: Integer); overload;
   procedure Execute; override;
       
   procedure TerminateConnection();
   function GetResult: Boolean;
   function GetResultString: String;
 end;

 TProxyChecker = class(TThread)
 private
   fUrl: String;
   fCheckString: String;
   fServer: String;
   fPort: Integer;
   fTimeout: Integer;
   fResult: Boolean;

   //Result variables
   fProxyType: TProxyType;
   fProxyLatency: Integer;
   fProxySSLSupport: Boolean;
   fProxyCountryCode: String;
   fProxyCountryName: String;
 protected
   ...удалено для облегчения кода...
 public
   constructor Create(Url, CheckString, Server: String;
     Port, Timeout: Integer); overload;
   procedure Execute; override;

   function GetResult: Boolean;
   function GetProxyType: TProxyType;
   function GetLatancy: Integer;
 end;

implementation

//======================================================
//TProxyCheckerThread BEGIN
//======================================================

constructor TProxyCheckerThread.Create(Url, Server: String; Port: Integer);
begin
 //Create thread in suspending mode
 inherited Create(True);

 fResult := false;
 fResultString := "";
 //Create TIdHTTP component to retrive information
 fIdHTTP := TIdHTTP.Create(nil);
 fIdHTTP.Request.ProxyServer := Server;
 fIdHTTP.Request.ProxyPort := Port;
 fUrl := Url;
 Resume;
end;

procedure TProxyCheckerThread.Execute;
begin
 try
   fResultString := fIdHTTP.Get(fUrl);
 except
   //Error in connection
   exit;
 end;
 fResult := True;
end;

procedure TProxyCheckerThread.TerminateConnection;
begin
 fIdHTTP.DisconnectSocket;
 fIdHTTP.Free;
end;

...удалено для облегчения кода...

//======================================================
//TProxyCheckerThread END
//======================================================

//======================================================
//TProxyChecker BEGIN
//======================================================

constructor TProxyChecker.Create(Url, CheckString, Server: String;
 Port, Timeout: Integer);
begin
 //Create thread in suspending mode
 inherited Create(True);
 //Set thread"s settings and execute it
 //Priority := tpIdle;
 fUrl := Url;
 fCheckString := CheckString;
 fServer := Server;
 fPort := Port;
 fTimeout := Timeout;
 //Prepare global variables
 fResult := false;
 fProxyType := ptBad;
 Resume;
end;

procedure TProxyChecker.Execute;
 var
   PCT: TProxyCheckerThread;
   StartTime: Integer;
   timePass: Integer;
begin
 //Prepare thread and execute to recieve result from URL
 PCT := TProxyCheckerThread.Create(fUrl, fServer, fPort);
 StartTime := GetTickCount;
 timePass := 0;
 //Work until time will finish and result from page will be recieved
 while (timePass < fTimeout) and (not PCT.GetResult) do begin
   timePass := GetTickCount-StartTime;
   //Wait for result or timeout
 end;
 //Checking if result exist and analyze it
 if PCT.GetResult then begin
   fProxyLatency := timePass;
   AnalyzeResult(PCT.GetResultString);
 end else
   fProxyType := ptBad;
 //Remove thread, even in case if it still running ;-)
 //First of all of cause terminate any connection
 PCT.TerminateConnection;
 PCT.Terminate;
 PCT.Free;
 fResult := True;
end;

...удалено для облегчения кода...

//======================================================
//TProxyChecker END
//======================================================
end.


 
Error_   (2006-06-02 04:19) [40]

Это думаю одно из тех вешей что мне точно поможет :-) TThreadList сейчас пошел читать доку по этому классу
Спасибо
п.с. Как на счет моего кода, есть баги?


 
Delphi5.01 ©   (2006-06-02 04:56) [41]

Сегодняшние тестирования вообще удивили меня, программа после того как были запушены/уничтожены в суме около 300 поток вырубилась. Снова не показав ошибку. Даже не могу представить как можно отследить в такой ситуации ошибку и где она вообще возникает :-( Не говоря о том что не возможности следить за состоянием потоков. Думаю продеться вести файл лог, хотя не совсем представляю что за информацию туда писать :-(



Страницы: 1 2 вся ветка

Форум: "WinAPI";
Текущий архив: 2006.10.22;
Скачать: [xml.tar.bz2];

Наверх





Память: 0.6 MB
Время: 0.039 c
4-1149681830
Foks2003
2006-06-07 16:03
2006.10.22
По поводу окон Windows


1-1157899111
Kolan
2006-09-10 18:38
2006.10.22
Управление свойством Point TChart.


2-1160392230
syte_ser78
2006-10-09 15:10
2006.10.22
popupmenu


6-1148556769
Andrey_r
2006-05-25 15:32
2006.10.22
IdFTP


15-1159866468
VitV
2006-10-03 13:07
2006.10.22
Посоветуйте програму.





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский