В настоящее время мы переходим в систему для использования WCF и столкнулись с проблемой, которую мы не можем выяснить.Установка состоит из C # DLL-файла, который оборачивает C ++ и Visual Basic 6.0 DLL-файл.DLL-файл C # имеет обертки для обоих из них и создает экземпляры обоих объектов.Объект C ++ инициализируется (получает данные из файлов) и затем передается в объект Visual Basic 6.0, который запускает отчет, используя данные в объекте C ++.Все это происходит как приложение-служба WCF, и по большей части оно прекрасно работает, но когда код Visual Basic 6.0 вызывает метод в объекте C ++, все зависает.
Я протестировал, используя толькопростое приложение, которое вызывает тот же файл DLL C # (за пределами WCF) и работает без нареканий.Итак, что-то происходит с WCF и этим C ++ DLL-файлом, но мы не можем понять, что именно.Я изменил DLL-файл Visual Basic 6.0, чтобы использовать Запустить без присмотра и Хранить в памяти (чтобы иметь возможность использовать его в многопоточном режиме), но, похоже, это не имеет значения.
Кто-нибудь имел опыт с этим, или есть какие-либо мысли о том, почему это будет зависать?Я думаю, что служба WCF каким-то образом блокирует файл DLL, и поэтому, когда файл DLL Visual Basic 6.0 использует его, он не может получить к нему доступ, что приводит к его взаимоблокировке.
C ++ Wrapper
public interface ISummaryWrapper
{
void LoadInfo(Application info);
SummaryApp GetSummary();
}
public class SummaryWrapper : ISummaryWrapper
{
private SummaryApp _summary;
public SummaryWrapper()
{
_summary = new SummaryApp();
}
public SummaryWrapper(Application info)
{
_summary = new SummaryApp();
LoadInfo(info);
}
public void LoadInfo(Application info)
{
_summary.Initialize(info);
}
public SummaryApp GetSummary()
{
return _summary;
}
}
Информационный объект содержит информацию о том, что должен сгенерировать объект Summary.Он используется только в методе Initialize.
Объект Visual Basic 6.0 загружается через интерфейс:
public void LoadPageObject(Application info)
{
_pageInfo = new PageInformation();
_pageInfo.oInfo = info;
_pageInfo.oSummary = _summary;
}
Так что теперь объект PageInformation объекта Visual Basic 6.0 имеет объект сводки.
Далее мы вызываем метод для создания отчета:
_pageInfo.BuildReport();
Это происходит внутри DLL-файла Visual Basic 6.0, и в тот момент, когда код пытается использовать объект сводки, он зависает
// Omitted actual params for brevity, though all the params exist
double value = oSummary.GetData(string parm1, string parm2)
Если я использую этот же вызов в C #, он просто отлично отбрасывает данные.
double value = _summary.GetData(string parm1, string parm2);
Опять же, когда я использую эту оболочку вне WCF, она проходит через кодхорошо.Только когда он работает в WCF, он зависает.
Кажется, что это проблема в MTA, и я не уверен, можно ли настроить приложение службы WCF, работающее на IIS, для работы в STA.Это возможно?
РЕШЕНО: Я нашел свой ответ в этом вопросе переполнения стека:
Как сделать службу WCF STA (однопоточной)
Что привело меня к статье XXX .
По сути, мне пришлось создать поток, настроенный на STA, и запуститьAPI (мой файл C # DLL) в нем.Поскольку все это выполняется с помощью TaskFactory (поэтому я могу отменить вызовы и выполнить несколько запросов), это было немного сложно.Теперь у меня все еще есть возможность запускать несколько отчетов одновременно в MTA, но каждый отчет выполняется в STA.Кроме того, я не теряю свою функциональность отмены от WCF.
Вот код (мне нужно еще кое-что очистить):
public class Builder
{
public string OperationId { get; set; }
public IServiceCallback CallBack { get; set; }
public Dictionary<string, CancellationTokenSource> Queue { get; set; }
public void BuildReport()
{
OperationContext context = OperationContext.Current;
Thread thread = new Thread(
new ThreadStart(
delegate
{
using (OperationContextScope scope = new OperationContextScope(context))
{
try
{
CancellationToken token = Queue[OperationId].Token;
CallBack.SendStatus(OperationId, Status.Processing);
IAPI api = new API(token);
api.MessagingEvents += MessageEvent;
// Build Report
CallBack.SendStatus(OperationId, Status.BuildingReport);
if (!api.BuildReport())
return;
CallBack.SendStatus(OperationId, Status.Completed);
}
catch (OperationCanceledException oc)
{
// Sending this on the method that receives the cancel request, no need to send again
}
catch (Exception ex)
{
// May not be able to use callback if it's a Timeout Exception, log error first
// TODO: Log Error
CallBack.SendMessage(OperationId, MessageType.Error, ex.Message);
CallBack.SendStatus(OperationId, Status.Error);
}
finally
{
Queue.Remove(OperationId);
}
}
}));
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
}
}
И моя служба вызывает это через:
// I initialize taskfactory when the service is created, omitting other code for brevity
public void BuildReport(ReportRequest request)
{
CallBack.SendReportStatus(request.OperationId, Status.Received);
CancellationTokenSource cancelSource = new CancellationTokenSource();
Queue.Add(request.OperationId, cancelSource);
Builder builder = new Builder
{
OperationId = request.OperationId,
CallBack = CallBack,
Queue = _queue
};
_taskFactory.StartNew(builder.BuildReport, cancelSource.Token);
}
Надеюсь, это поможет всем, кто сталкивается с этой проблемой!