Как мне делегировать метод AsyncCallback для Control.BeginInvoke? (.СЕТЬ) - PullRequest
5 голосов
/ 27 ноября 2009

Можно ли использовать Control.BeginInvoke для чего-либо, кроме как «запустить и забыть»? Я хочу изменить следующий запрос, чтобы делегировать метод обратного вызова, чтобы я мог что-то сделать после завершения каждого из моих асинхронных вызовов.

this.BeginInvoke(new RefreshRulesDelegate(RefreshRules), new object[] { ctrl, ctrl.DsRules, ctrl.CptyId });  

Я мог бы сделать это с обычным делегатом. Например, BeginInvoke.

RefreshRulesDelegate del = new RefreshRulesDelegate(RefreshRules);
            del.BeginInvoke(ctrl, ctrl.DsRules, ctrl.CptyId, new AsyncCallback(RefreshCompleted), del);  

Но из-за того, что я звоню Control .BeginInvoke, я не могу этого сделать, так как получаю ошибку «операция с несколькими потоками не действительна».
Кто-нибудь поможет?

В дополнение к некоторым из полученных ответов я поясню «почему». Мне нужно загрузить / обновить элемент управления в моем графическом интерфейсе без блокировки остальной части приложения. Элемент управления содержит множество элементов управления (ruleListCtls), которые требуют, чтобы набор данных был извлечен и передан им. т.е.

public void RefreshAll()
{
    foreach (LTRFundingRuleListControl ctrl in ruleListCtls)
    {
        this.BeginInvoke(new RefreshRulesDelegate(RefreshRules), new object[]{ctrl,ctrl.DsRules, ctrl.CptyId });   
    }
}  

Я обнаружил, что могу сделать это, если я предоставлю метод обратного вызова делегата и перенесу любой код, который исправляет элементы управления, обратно в основной поток GUI, в котором они были созданы (чтобы избежать ошибки между потоками)

public void RefreshAll()
{
    IntPtr handle; 
    foreach (LTRFundingRuleListControl ctrl in ruleListCtls)
    {
        handle = ctrl.Handle;
        RefreshRulesDsDelegate del = new RefreshRulesDsDelegate(RefreshRulesDs);
        del.BeginInvoke(ctrl.DsRules, ctrl.CptyId, handle, out handle, new AsyncCallback(RefreshCompleted), del);
    }        
}

private void RefreshCompleted(IAsyncResult result)
{
    CptyCatRuleDataSet dsRules;
    string cptyId;
    IntPtr handle;

    AsyncResult res = (AsyncResult) result;

    // Get the handle of the control to update, and the dataset to update it with
    RefreshRulesDsDelegate del = (RefreshRulesDsDelegate) res.AsyncDelegate;
    dsRules = del.EndInvoke(out handle,res);

    // Update the control on the thread it was created on
    this.BeginInvoke(new UpdateControlDatasetDelegate(UpdateControlDataset), new object[] {dsRules, handle});
}

public delegate CptyCatRuleDataSet RefreshRulesDsDelegate(CptyCatRuleDataSet dsRules, string cptyId, IntPtr ctrlId, out IntPtr handle);
private CptyCatRuleDataSet RefreshRulesDs(CptyCatRuleDataSet dsRules, string ruleCptyId, IntPtr ctrlId, out IntPtr handle)
{
    try
    {
        handle = ctrlId;
        int catId = ((CptyCatRuleDataSet.PSLTR_RULE_CAT_CPTY_SelRow)dsRules.PSLTR_RULE_CAT_CPTY_Sel.Rows[0]).RULE_CAT_ID;
            return ltrCptyRulesService.GetCptyRules(ruleCptyId, catId);
    }
    catch (Exception ex)
    {
        throw ex;
    }
}  

Вот что мы переходим к основному потоку, получившему обратный вызов:

private delegate void UpdateControlDatasetDelegate(CptyCatRuleDataSet dsRules, IntPtr ctrlId);
private void UpdateControlDataset(CptyCatRuleDataSet dsRules, IntPtr ctrlId)
{
    IEnumerator en = ruleListCtls.GetEnumerator();
    while (en.MoveNext())
    {
        LTRFundingRuleListControl ctrl = en.Current as LTRFundingRuleListControl;
        if (ctrl.Handle == ctrlId)
        {
            ctrl.DsRules = dsRules;
        }
    }
}  

Теперь это работает нормально. Тем не менее, главная проблема, кроме того, что я не думаю, что это особенно элегантно, это обработка исключений. Может быть, это другой вопрос, но если RefreshRulesDs выдает исключение, то мое приложение вылетает, поскольку ошибка не копируется обратно в поток GUI (очевидно), а как необработанное исключение. Пока я не смогу их поймать, мне придется выполнять всю эту операцию синхронно. Как мне успешно отловить ошибку и загрузить остальные мои элементы управления? Или как мне выполнить эту асинхронную операцию другим способом с надлежащей обработкой исключений?

Ответы [ 3 ]

4 голосов
/ 27 ноября 2009

Относительно части «Возможно ли»: Нет, Control.BeginInvoke использует Windows PostMessage(), и это означает, что ответа нет. Это также означает, что RefreshRulesDelegate выполняется в основном потоке, а не в фоновом потоке.

Итак, используйте delegate.BeginInvoke или ThreadPool, а когда они будут завершены, используйте Control.[Begin]Invoke() для обновления интерфейса.

2 голосов
/ 27 ноября 2009

Вы можете сделать это:

this.BeginInvoke(delegate
{
    RefreshRules(ctrl, ctrl.DsRules, ctrl.CptyId);
    RefreshCompleted();
});

EDIT:

Я бы рассмотрел удаление аргумента IAsyncResult из метода RefreshCompleted и использовал приведенное выше решение.

Если по какой-то причине вам действительно нужно сохранить аргумент IAsyncResult. Вы можете реализовать метод расширения для Control:

public static IAsyncResult BeginInvoke(this Control control, Delegate del, object[] args, AsyncCallback callback, object state)
{
    CustomAsyncResult asyncResult = new CustomAsyncResult(callback, state);
    control.BeginInvoke(delegate
    {
        del.DynamicInvoke(args);
        asyncResult.Complete();
    }, args);

    return asyncResult;
}

public static void EndInvoke(this Control control, IAsyncResult asyncResult)
{
    asyncResult.EndInvoke();
}

Вам необходимо определить свой класс CustomAsyncResult, вы можете получить документацию о том, как это сделать здесь

0 голосов
/ 27 ноября 2009

То есть вы хотите, чтобы "дополнительная вещь" произошла в рабочем потоке? (иначе вы просто запустите его в методе RefreshRules). Возможно, просто используйте ThreadPool.QueueUserItem:

ThreadPool.QueueUserWorkItem(delegate { /* your extra stuff */ });

в конце (или после) вашего RefreshRules метода?

Для информации, вам может быть проще / аккуратнее вызвать BeginInvoke с анонимным методом тоже:

this.BeginInvoke((MethodInvoker) delegate {
    RefreshRules(ctrl, ctrl.DsRules, ctrl.CptyId);
    ThreadPool.QueueUserWorkItem(delegate { /* your extra stuff */ });
});

это позволяет избежать создания типа делегата и обеспечивает проверку типов при вызове RefreshRules - обратите внимание, что он захватывает ctrl, однако - поэтому, если вы находитесь в цикле, вам потребуется копия:

var tmp = ctrl;
this.BeginInvoke((MethodInvoker) delegate {
    RefreshRules(tmp, tmp.DsRules, tmp.CptyId);
    ThreadPool.QueueUserWorkItem(delegate { /* your extra stuff */ });
});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...