Указатель на функцию
Вопрос собственно по указателям.
Поскольку я в C# недавно, то с делегатами пока не очень и потому прошу Вашей помощи.
Есть код, который вызывает функцию FRun (третья строчка снизу):
DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
if (worker.CancellationPending)
{
e.Cancel = true;
}
else
{
int n = (int)e.Argument;
int percentComplete =
(int)((float)n / (float)numberToCompute * 100);
if (percentComplete > highestPercentageReached)
{
highestPercentageReached = percentComplete;
worker.ReportProgress(percentComplete);
}
e.Result = FRun(n, worker, e);
};
}
Тело функции FRun может быть разным и описывается в другом модуле (это нужно для работы различных алгоритмов в отдельном потоке)
Представленный метод является частью класса формы
Я хочу вызывать это окно из любого модуля с передачей указателя (делегата) на функцию в конструкторе:
Тем самым FRun всегда отработает тело той функции, которую мы передали по указателю.
Теперь у меня пошли затруднения.
Как я понимаю, сначала необходимо описать указатель на функцию как тип.
Правильно ли это не знаю, но думаю что так:
А теперь необходимо собственно написать эту функцию и передать ее в конструктор формы.
Для простоты функция будет просто выдавать число (например 5).
Конструктор формы:
public F_Wait(FHandler F)
{
InitializeComponent();
InitializeBackgoundWorker();
FRun += new FHandler(F);
}
Правильно ли это? И как написать саму функцию, в которая будет тут отрабатывать и передать ее в конструктор?
Класс формы FormWithProgressBar. На форме progressBar1 (для показа прогресса), button1 (кнопка "Отмена"), backgroundWorker1 (инкапсулятор потока). Компонентам назначены соответствующие события.
Свойства backgroundWorker1.WorkerReportsProgress и WorkerSupportsCancellation установлены в true.
Свойство DialogResult у button1 установлен в DialogResult.Cancel.
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication9 {
public partial class FormWithProgressBar : Form {
public FormWithProgressBar(WorkerHandler worker_handler) {
if (ReferenceEquals(null, worker_handler))
throw new ArgumentNullException("worker_handler");
InitializeComponent();
this.worker_handler = worker_handler;
}
private WorkerHandler worker_handler;
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) {
progressBar1.Value = e.ProgressPercentage;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
DialogResult = e.Cancelled ? DialogResult.Cancel : DialogResult.OK;
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {
e.Cancel = !worker_handler(backgroundWorker1);
}
private void FormWithProgressBar_Load(object sender, EventArgs e) {
backgroundWorker1.RunWorkerAsync();
}
public static bool RunBackgroundWork(WorkerHandler handler) {
using (FormWithProgressBar form = new FormWithProgressBar(handler)) {
return form.ShowDialog() == DialogResult.OK;
}
}
private void FormWithProgressBar_FormClosing(object sender, FormClosingEventArgs e) {
if (backgroundWorker1.IsBusy) {
backgroundWorker1.CancelAsync();
e.Cancel = true;
}
}
private void button1_Click(object sender, EventArgs e) {
backgroundWorker1.CancelAsync();
}
}
public delegate bool WorkerHandler(BackgroundWorker worker);
}
Вот пример использования:
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace WindowsFormsApplication9 {
public partial class MainForm : Form {
public MainForm() {
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e) {
Text = "runnning task1";
bool result = FormWithProgressBar.RunBackgroundWork(delegate(BackgroundWorker woker) {
for (int i = 0; i < 100; ++i) {
Thread.Sleep(100);
woker.ReportProgress(i);
if ( (i % 10 == 0) && woker.CancellationPending) {
return false;
}
}
return true;
});
Text = result ? "Succeded" : "Canceled";
}
}
}
В дополнении уже вышеописанному необходимо написать тело функции, которое точно повторяет вид делегата, например:
Объявление делегата:
Код функции:
{
long r;
//Код
return (r)
}
Описание конструктора формы и некоторых полей:
private int highestPercentageReached = 0;
private FHandler FRun;
public F_Wait(FHandler F, int NumberToCompute)
{
InitializeComponent();
InitializeBackgoundWorker();
FRun = F;
FNumberToCompute = NumberToCompute;
}
Теперь вызываем вызываем конструктор класса-окна и передаем туда указатель на функцию:
Wait.ShowDialog(this);
Единственно, что не заработало (конечно это можно оформить как отдельную тему)
то тот момент, что при выполнении алгоритма переданной по указателю функции, эта форма "подвисает" и расположенная на ней кнопка "Отмена" недоступна для нажатия.
Вот обработчик на нажатие кнопки "Отмена":
{
backgroundWorker.CancelAsync();
}
Вот для тестирования написал эту функцию:
{
int i = 1;
int j = 0;
for (i = 1; i <= 100000; i++)
{
if (worker.CancellationPending)
{
e.Cancel = true;
}
else
{
if (i == 10000) j = 10;
if (i == 20000) j = 20;
if (i == 30000) j = 30;
if (i == 40000) j = 40;
if (i == 50000) j = 50;
if (i == 60000) j = 60;
if (i == 70000) j = 70;
if (i == 80000) j = 80;
if (i == 90000) j = 90;
if (i == 100000) j = 100;
worker.ReportProgress(j);
}
}
return (i);
}
Что интересно, прогрессбар работает как надо, несмотря на то, что окно вцелом находится в "подвисшем состоянии", хотя работа этой функции вынесена в отдельный поток.
Я смотрю Вы помимо инкапсулятора потока явно используете класс-поток и вызываете
Может все дело в этой строчке и поэтому Ваше окно не подвисает?
Каким образом можно сделать так, чтобы на кнопку "Отмена", расположенную в этом окне, можно было нажать?
то тот момент, что при выполнении алгоритма переданной по указателю функции, эта форма "подвисает" и расположенная на ней кнопка "Отмена" недоступна для нажатия.
Вы слишком часто обращаетесь к worker-у, а именно на каждой итерации: сто тысяч раз. Обращение к свойству CancellationPending и методу ReportProgress приводят к синхронизации с потоком интерфейса, что приводит к "замораживанию" формы - поток интерфейса не обрабатывает сообщения из очереди ввиду того что происходят (в вашем случае) некоторые сервисные действия в выделенном потоке BackgroundWorker-а.
Возвращаясь к своему примеру привожу код, выполняющий аналогичную задачу без неприятного эффекта:
Text = "runnning task1";
bool result = FormWithProgressBar.RunBackgroundWork(MakeComputationWorker(100000000));
Text = result ? "Succeded" : "Canceled";
}
private static WorkerHandler MakeComputationWorker(int n) {
return delegate(BackgroundWorker worker) {
int percent = n / 100;
for (int i = 1; i <= n; ++i) {
if (i % percent == 0) {
if (worker.CancellationPending)
return false; // сообщаем о прерывании
worker.ReportProgress(i / percent);
}
}
return true; // все OK - задача завершена
};
}
Кстати, я тут нашел местечко, череватое багами - метод backgroundWorker1_ProgressChanged, вот подправленная версия:
progressBar1.Value = Math.Min(progressBar1.Maximum, Math.Max(progressBar1.Minimum, e.ProgressPercentage));
}
return delegate(BackgroundWorker worker) {
Не понимаю зачем возвращать WorkerHandler
Не понимаю как тут работает return delegate(BackgroundWorker worker)
{
int percent = n / 100;
for (int i = 1; i <= n; ++i)
{
if (i % percent == 0)
{
if (worker.CancellationPending)
return false; // сообщаем о прерывании
worker.ReportProgress(i / percent);
}
}
return true;
}
И затем вызов:
Wait.ShowDialog(this);
То все работает как часы. Огромное Вам спасибо за помощь!
Как я понял, главное не делать частый вызов worker'а, правильно?
А почему если часто вызывать, то как Вы сказали происходит синхронизация потоков, но почему при этом окно не обрабатывает сообщения?
Поток интерфейса занимается выборкой сообщений ОС из очереди. Во время синхронизированного вызова этот поток фактически блокируется - ожидается выход другого потока (рабочий поток BackgroundWorker) из синхронизированного контекста. Если очень часто производить подобную синхронизацию, эффект будет равносилен выполнению длительного цикла в обработчике щелчка мышки на кнопке: сообщения никуда не пропадут, они будут обработаны (и очень быстро!) как только поток интерфейса перестанут прерывать.
return delegate(BackgroundWorker worker) {
Не понимаю как тут работает return delegate(BackgroundWorker worker)
Конструкция вида delegate(BackgroundWorker worker) { ... }; называется анонимной функцией - лямбдой. Тип возвращаемого ею значения выводится из использования предложения return в теле (в примере: return true; т.е. System.Boolean), и он эквивалентен типу-делегатов WorkerHandler.
Вообще, показанная конструкция очень похожа на механизм каррирования в функциональных языках.
Основная задача кода - это передать дополнительный параметр (n) в делегат, определение которого не предполагает такого параметра. Т.е. создается функция, которая возвращает функцию нужного типа, замкнутую на лексический контекст, в котором этот параметр присутствует. Таким образом, наша лямбда теперь знает о параметре n. Это иллюстрация механизма замыканий.
Конструкция вида delegate(BackgroundWorker worker) { ... }; называется анонимной функцией - лямбдой. Тип возвращаемого ею значения выводится из использования предложения return в теле (в примере: return true; т.е. System.Boolean), и он эквивалентен типу-делегатов WorkerHandler.
Вообще, показанная конструкция очень похожа на механизм каррирования в функциональных языках.
Основная задача кода - это передать дополнительный параметр (n) в делегат, определение которого не предполагает такого параметра. Т.е. создается функция, которая возвращает функцию нужного типа, замкнутую на лексический контекст, в котором этот параметр присутствует. Таким образом, наша лямбда теперь знает о параметре n. Это иллюстрация механизма замыканий.
Я с этим еще не встречался. Это меня очень заинтересовало, обязательно об этом почитаю.
Вижу Вы очень начитаны о различных приемах и технологиях программирования.
Можно Вам тогда вопрос не по теме: в Delphi например, можно создавать функции (процедуры) внутри других функций (процедур). Соответственно время жизни и область видимости таких функций - это функция, в которую она заключена. А можно ли подобные конструкции создавать в C#?
Можно. И в некоторых случаях даже нужно. Однако, компилятор C# достаточно туповат и не умеет автоматически создавать определения типов-делегатов для анонимных функций, каждую анонимную функцию нужно приводить (явно или неявно) к тому или иному типу делегата.
Сама платформа не предоставляет готовых типов делегатов, но их можно легко определить самому. У меня есть специальный набор делегатов для определения вложенных функций и процедур (функций, возвращающих void).
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication24 {
using Hardcase.Common.Utils;
class Program {
static void Main(string[] args) {
Proc<int, string> PrintIntString = delegate(int x, string s) { //неявное приведение типа делегата
Console.WriteLine("Int: {0}, String: {1}", x, s);
};
Func<int, int, int> Sum = delegate(int a, int b) {
return a + b;
};
PrintIntString(Sum(10, 23), "Hello world!");
Console.ReadLine();
}
}
}
namespace Hardcase.Common.Utils {
public delegate void Proc();
public delegate void Proc<TParam1>(TParam1 param1);
public delegate void Proc<TParam1, TParam2>(TParam1 param1, TParam2 param2);
public delegate void Proc<TParam1, TParam2, TParam3>(TParam1 param1, TParam2 param2, TParam3 param3);
public delegate void Proc<TParam1, TParam2, TParam3, TParam4>(TParam1 param1, TParam2 param2, TParam3 param3, TParam4 param4);
public delegate void Proc<TParam1, TParam2, TParam3, TParam4, TParam5>(TParam1 param1, TParam2 param2, TParam3 param3, TParam4 param4, TParam5 param5);
public delegate TResult Func<TResult>();
public delegate TResult Func<TResult, TParam1>(TParam1 param1);
public delegate TResult Func<TResult, TParam1, TParam2>(TParam1 param1, TParam2 param2);
public delegate TResult Func<TResult, TParam1, TParam2, TParam3>(TParam1 param1, TParam2 param2, TParam3 param3);
public delegate TResult Func<TResult, TParam1, TParam2, TParam3, TParam4>(TParam1 param1, TParam2 param2, TParam3 param3, TParam4 param4);
public delegate TResult Func<TResult, TParam1, TParam2, TParam3, TParam4, TParam5>(TParam1 param1, TParam2 param2, TParam3 param3, TParam4 param4, TParam5 param5);
}
Метод Main в C# 3.0 можно переписать в виде (исполняемый код будет совершенно идентичным):
var PrintIntString = (Proc<int, string>) delegate(int x, string s) { // явное приведение типа через оператор ()
Console.WriteLine("Int: {0}, String: {1}", x, s);
};
var Sum = (Func<int, int, int>) delegate(int a, int b) {
return a + b;
};
PrintIntString(Sum(10, 23), "Hello world!");
Console.ReadLine();
}
Время жизни таких процедур/функций не ограничено текущим стековым фреймом, как в Делфи, так как при конструировании каждого делегата в фоне создается экземпляр специального автоматически сгенерированного компилятором класса, членом которого этот делегат и является. Объект будет доступен для сборщика мусора когда пропадет пояследняя ссылка на него, зачастую это происходит при выходе из основной функции, в этом случае поведение среды совпадает с семантикой вложенных функций Delphi. При острой жажде знаний утиллита ildasm.exe покажет всю подноготную анонимных делегатов.
Для процедур в C# 3.0 можно использовать Action<> и мне почему-то ближе такой синтаксис:
{
Console.WriteLine("Int: {0}, String: {1}", x, s);
};
Func<int, int, int> Sum = (a, b) =>
{
return a + b;
};
Почему я об этом спрашиваю, так это просто в рамках одной задачи, которую я выполняю. Есть еще один момент, которые очень интересен. Мы разобрались с работой BackgroundWorker'а, но я обнаружил, что некоторые функции которые прекрасно работают в основном потоке, но если их вынести в отдельный поток, то они перестают работать. Примером того служит работа Interop с вызовом отчета из MS Access'а:
Access.Dao.Database db = null;
try
{
object ObjOpt = System.Reflection.Missing.Value;
App = new Access.Application();
App.OpenCurrentDatabase("\\\\Template\\App.accdb", false, null);
Access.Dao.DBEngine dbEngine = new Access.Dao.DBEngine();
db = dbEngine.OpenDatabase("\\\\Template\\App.accdb", ObjOpt, false, ObjOpt);
db.QueryDefs["Отчет"].SQL = "exec dbo.USP_Rep_Trades_Information \"" + Form.DateParam.ToString("dd.MM.yyyy") + "\"";
SaveFileDialog SFD = new SaveFileDialog();
SFD.Filter = "Файлы ADOBE PDF (*.pdf)|*.pdf|Все файлы (*.*)|*.*";
SFD.FileName = SFD.FileName + "Отчет.pdf";
if (SFD.ShowDialog(this) == DialogResult.OK)
{
Cursor = System.Windows.Forms.Cursors.WaitCursor;
App.DoCmd.OutputTo(
Microsoft.Office.Interop.Access.AcOutputObjectType.acOutputReport,
"Отчет",
"PDF",
SFD.FileName,
Form.AutoStart,
ObjOpt,
ObjOpt
);
};
}
catch (Exception Ex)
{
MessageBox.Show(
this,
Ex.Message,
@"Формирование отчета ""Отчет""",
MessageBoxButtons.OK,
MessageBoxIcon.Error
);
}
finally
{
if (db != null)
{
db.Close();
db = null;
};
if (App != null)
{
App.CloseCurrentDatabase();
App = null;
};
GC.Collect();
GC.WaitForPendingFinalizers();
Cursor = System.Windows.Forms.Cursors.Arrow;
};
При выполнении этого кода отдельным поток возникает ошибка, скриншот которой представлен во вложении.
Ошибка происходит на строчке:
Спасибо, не знал. :) Видимо просто привык к собственным классам, которые тащу из .NET 2.0.
и мне почему-то ближе такой синтаксис
{
Console.WriteLine("Int: {0}, String: {1}", x, s);
};
Синтаксис все равно ужасен. Представьте, что вместо int и string стоит что-то в духе SomeComplexLongNameClass. В таких случаях нотация с var чуточку удобнее так как сразу пишется имя функции:
Console.WriteLine("Int: {0}, String: {1}", x, s);
});
Функцональные возможности (вывод типов) в C# кастрированы до безумия. Для кода
Console.WriteLine("First: {0}, Second: {1}", x, s);
};
[quote=csc.exe]
Cannot assign lambda expression to an implicitly-typed local variable [/quote]
Пусть даже я и укажу типы аргументов, он всеравно будет рассказывать это сообщение.
Исключительно для сравнения приведу, как такие конструкции должны выглядеть по-человечески.
Nemerle:
Console.WriteLine($"First: $x, Second: $s");
}
Console.WriteLine("First: {0}, Second: {1}", x, s)
Кстати возможно dynamic в четвертом C# исправит эту ситуацию - по сути им остается упростить синтаксис, чтобы не указывать dynamic в Func<..> и получится почти полноценный вывод типа?
P.S. давно смотрю в сторону Nemerle (и F# немного ковырял) но все не хватает времени и решимости попробовать их в деле :)
C# 4.0
...
Func Test = (x, y) => Console.WriteLine("First: {0} Second: {1}", x, y);
Test(1, 3);
Работает и имеет вполне приличный синтаксис :)
Не нужно только фантазировать - Intellisense работает прекрасно. И типы аргументов в Nemerle и F# выводятся как раз таки на этапе компиляции, в отличие от пресловутых dynamic типов в C# 4.0.
Кстати возможно dynamic в четвертом C# исправит эту ситуацию - по сути им остается упростить синтаксис, чтобы не указывать dynamic в Func<..> и получится почти полноценный вывод типа?
Динамики в C# 4.0 не выводятся на этапе компиляции, они из мира динамической типизации, простите за каламбур. Компилятор совершенно не парится о том, чего в нем передается - об этом заботится DLR на этапе работы программы. Т.е. ваш код фактически добавляет дополнительные проверки типов при работе программы, за кажущейся простотой нотации потенциально скрывается нехилый такой провал в производительности кода.
<holywar>
Я ортодоксален в вопросе типизации, и dynamic в C# вижу как преступление в отношении языка. Типизация обязана быть статической. Кроме того, существуют более важные парадигмы, которых нехватает C#: контакты из Spec# хотя бы. Вон в Sing# их же реализовали (да этом принципе вообще ОС Singularity построена), и притом очень неплохо.
</holywar>
P.S. давно смотрю в сторону Nemerle (и F# немного ковырял) но все не хватает времени и решимости попробовать их в деле :)
А я уже давно для себя решил - пора потихоньку сползать с C#. Пожалуй единственная красивая и стройная версия языка была C#2.0, в которой появились генерики. После этого начался кошмарный ужас - малополезный LINQ, половинчатое (и оттого корявое) ФП, попахивающее дикой попсятиной dynamic. А реально нужных вещий Хейлсберг в язык не вносит. Обидно, блин.
З.Ы. Статическая типизация для меня как наркотик. Совершенно не могу писать на JavaScript из-за того, что не чувствую опеки компилятора - нет никакого автоматизированного инструмента, способного хоть как-то оценить работоспособность и корректность кода без его запуска.
Ого, честно говоря я думал, что и в Nemerle и в F# вывод типа в рантайме происходит. Надо будет почитать на эту тему!
Это я знаю, поэтому сразу после написания проверил производительность этой конструкции - 30-кратное ухудшение (простой тест с циклом был, без заморочек - так что плюс/минус). Может ещё допилят, но все равно многовато конечно...
А я уже давно для себя решил - пора потихоньку сползать с C#. Пожалуй единственная красивая и стройная версия языка была C#2.0, в которой появились генерики. После этого начался кошмарный ужас - малополезный LINQ, половинчатое (и оттого корявое) ФП, попахивающее дикой попсятиной dynamic. А реально нужных вещий Хейлсберг в язык не вносит. Обидно, блин.
На счет 2.0 совершенно согласен, очень завершенным выглядел!
LINQ не пользуюсь, т.к. специфика работы далека от баз данных и запросов к ним... но тоже не понимаю в нем прикола!
А вот ФП мне понравилось, несколько раз уже в проекте было, когда удалось просто и элегантно решить проблему, которую без него конечно тоже можно было решить, но сильно уж извращенчески. Я понимаю, что это только огрызки ФП, но и они бывают порой полезны.
dynamic вообще только сейчас и вспомнил, просто интересно было можно ли заставить скомпилировать такой код =)
В общем надо наверное выделить время и попробовать Nemerle... Хотя мне с шарпа точно ещё рано слазить :)
Кроме того, существуют более важные парадигмы, которых нехватает C#: контакты из Spec# хотя бы.
Кажется они решили реализовать контракты на уровне BCL
http://blogs.msdn.com/bclteam/archive/2009/05/22/what-s-new-in-the-bcl-in-net-4-beta-1-justin-van-patten.aspx
Пред- и пост- условия, а также инварианты уже давно реализованы в Nemerle (пространство имен Nemerle.Assertions).